- PVSM.RU - https://www.pvsm.ru -

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

В прошлый раз мы подготовили Sketch-файлы, содержащие все иконки в нужных стилях и с правильными названиями. Пришёл черёд написания кода.
Достаточно сказать, что мы шли путём проб и ошибок. После того как наш тимлид Нихил Верма [2], заложивший основы скрипта, разработал ключевой исходный код, я запустил процесс, потребовавший не менее трёх фаз рефакторинга и множества модификаций. По этой причине я не буду вдаваться в подробности создания скрипта и сосредоточусь на том, как он работает сегодня, в финальном виде.
Написанный на Node.js скрипт сборки достаточно прямолинеен в своей работе: импортировав зависимости, объявив список Sketch-файлов для обработки (он представляет собой список брендов, каждому из которых сопутствует список относящихся к нему файлов) и убедившись в том, что на клиенте установлен Sketch, скрипт по очереди обрабатывает бренды, проделывая с каждым из них серию действий.
Соответствующие части скрипта сборки вы найдёте здесь:
// ... 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-файлов, которые позже будут использованы для экспорта ресурсов для различных платформ.
Файлы, ассоциированные с конкретным брендом (например, в случае с Blendr это файлы icons_common.sketch и icons_blendr.sketch) клонируются во временную папку (точнее в подпапку, названную по имени обрабатываемого бренда) и разархивируются.
Затем обрабатываются JSON-файлы. К названию ресурсов, подлежащих A/B-тестированию, добавляется префикс — таким образом, при экспорте они будут сохранены в подпапке с заранее указанным названием (соответствующим уникальному имени эксперимента). Понять, подлежит ли ресурс A/B-тестированию, можно по названию страницы, на которой он хранится: если подлежит, в названии будет содержаться префикс "XP_".

В примере выше экспортируемые ресурсы будут сохраняться в подпапке «this__is_an_experiment» с названием файла вида «icon-name[variant-name].ext».
Второй важный этап — извлечение всех необходимых метаданных из 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 [4]) для автоматического экспорта всех ресурсов в подходящих платформам форматах. Для каждого из ассоциируемых с брендом файлов (а вернее их клонированных и обновлённых версий) мы запускаем следующую команду:
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 — единственный (?) формат, который поддерживается Xcode и OS/iOS для импорта и визуализации векторных ресурсов (вот короткое объяснение [5] данного выбора Apple).
Поскольку мы можем напрямую экспортировать в PDF через SketchTool, никаких дополнительных действий не потребуется: просто сохраните файлы непосредственно в папке назначения, и всё.
В случае с 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 [6]).
Android поддерживает векторную графику с помощью кастомного векторного формата VectorDrawable [7]. Обычно конвертирование из SVG в VectorDrawable производится прямо в Android Studio [8]. Однако нам хотелось полностью автоматизировать процесс, поэтому мы искали способ конвертирования при помощи кода.
Изучив различные инструменты и библиотеки, мы остановились на svg2vectordrawable [9]. Она не только активно поддерживается (во всяком случае активнее, чем все остальные), но и более функциональна, чем остальные.
Реалии таковы, что VectorDrawable и SVG не одинаковы в своей функциональности: некоторые функции SVG (к примеру, радиальные градиенты и комплексное выделение) не поддерживаются VectorDrawable, а другие начали поддерживаться совсем недавно (начиная с версии Android API 24). Одна из вытекающих из этого проблем — то, что в более старых версиях (до 24) не поддерживается значение even-odd атрибута fill-rule [10]. Однако нам в 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.
После того как файлы ресурсов сохранены в конечном формате, остаётся лишь собрать всю полученную в ходе сборки метаинформацию и сохранить её в «словарь», чтобы использовать, когда ресурсы будут импортироваться и использоваться кодовой базой различных платформ.
После извлечения плоского списка иконок из объекта assetsMetadata мы проходимся по нему, проверяя каждую из них:
tabbar-livestream); если да, то просто оставляем его;
abtests После завершения данного процесса словарь будет содержать список всех базовых иконок (и их 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 [12] и другие подобные инструменты, которые мы могли бы использовать в рабочем процессе и своих конвейерах.
Я, как и многие другие [13], всегда стремился [14] к такому (идеальному) процессу:

Однако должен признаться, что я начал сомневаться в том, что Sketch — подходящий инструмент, особенно с учётом дизайн-системы. Поэтому я стал присматриваться к другим сервисам, таким как Figma с его открытыми API и Framer X с удобной интеграцией с React, поскольку не чувствовал у Sketch движения к интеграции с кодом (каким бы он ни был).
Так вот, этот проект меня переубедил. Не полностью, но во многом.
Пусть Sketch и не открывает свои API, но само устройство внутренней структуры его файлов служит своего рода «неофициальным» API. Создатели могли бы использовать зашифрованные названия или скрывать ключи в JSON-объектах, но вместо этого они придерживаются понятного, читабельного и концептуального соглашения о наименовании. Не думаю, что это случайность.
Тот факт, что Sketch-файлами можно управлять подобным образом, открыл мне дорогу ко множеству будущих разработок и улучшений: от плагинов для проверки наименования, стилизации и структуры слоёв для иконок до интеграции с нашей Wiki и документацией нашей дизайн-системы (обоюдной). Создав Node-приложения на Electron [15] или Carlo [16], мы можем облегчить для дизайнеров процесс выполнения множества рутинных действий.
Одним из неожиданных (по крайней мере, для меня) бонусов стало то, что Sketch-файлы с иконками Cosmos стали «источником истины» — нечто похожее произошло и с дизайн-системой Cosmos. Если иконки нет, то её не существует в кодовой базе (вернее не должно существовать; но теперь мы знаем, что такие случаи — исключение). Теперь это кажется очевидным, но так было не всегда — опять же, по крайней мере, для меня.
Когда к нам пришло осознание того, что Sketch-файлами можно манипулировать, то, что начиналось как MVP-проект, обернулось глубоким погружением в их внутреннее устройство. Мы ещё не знаем, куда нас приведёт эта тропа, но пока нам сопутствует удача. Дизайнеры, разработчики, продакт-менеджеры, руководство — все сходятся в том, что нововведение позволит упростить работу каждому из нас и предотвратить возникновение многих ошибок. Кроме того, перед нами открылись двери к новым способам использования иконок.
И последнее: описанный мной конвейер был построен для наших нужд, а потому он адаптирован под наши реалии. Имейте в виду, что он может не отвечать требованиям вашей компании.
Главное, чем я хотел поделиться, — что всё возможно. Может быть, другим способом, с другими подходами и другими файловыми форматами, с меньшей сложностью (например, вам может не понадобиться поддержка нескольких брендов и A/B-тестирования), но теперь вы можете автоматизировать процесс доставки иконок с кастомным скриптом Node.js и Sketch.
Ищите свой способ! Это весело и довольно просто.
Этот огромный проект был разработан совместно с Нихилом Вермой [17] (Mobile Web), создавшим первую версию скрипта сборки, Артёмом Рудым [18] (Android) и Игорем Савельевым [19] (iOS), которые разработали скрипты для импорта и приёма ресурсов для своих платформ.
Спасибо, ребята! Работать с вами над проектом и претворять его в жизнь было настоящим удовольствием.
Автор: alxgutnikov
Источник [20]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/311320
Ссылки в тексте:
[1] по ссылке: https://habr.com/ru/company/badoo/blog/441898/
[2] Нихил Верма: https://medium.com/@nikhilverma
[3] Image: https://cdn-images-1.medium.com/max/1080/1*MMlSKZ8thXsD-DXjBf3Pqg.png
[4] в обёртке под Node: https://github.com/julianburr/sketchtool-cli
[5] вот короткое объяснение: https://www.quora.com/Why-does-Xcode-uses-PDF-for-vector-assets-instead-svg
[6] cheerio: https://github.com/cheeriojs/cheerio
[7] VectorDrawable: https://developer.android.com/reference/android/graphics/drawable/VectorDrawable
[8] прямо в Android Studio: https://developer.android.com/studio/write/vector-asset-studio#svg
[9] svg2vectordrawable: https://github.com/Ashung/svg2vectordrawable
[10] не поддерживается значение even-odd атрибута fill-rule: https://medium.com/thoughts-overflow/the-mystery-of-the-disappearing-holes-a-gripping-tale-of-using-svg-in-android-442f6035a452
[11] Image: https://cdn-images-1.medium.com/max/1080/1*78Q-8rUaakgq7I6o6aWlzw.png
[12] html-sketchapp: https://github.com/brainly/html-sketchapp
[13] как и многие другие: https://medium.com/seek-blog/a-unified-styling-language-d0c208de2660
[14] всегда стремился: https://badootech.badoo.com/from-zero-to-cosmos-part-2-97929e13f839
[15] Electron: https://electronjs.org/
[16] Carlo: https://github.com/GoogleChromeLabs/carlo/
[17] Нихилом Вермой: https://twitter.com/NikhilVerma
[18] Артёмом Рудым: https://twitter.com/rudoj_artem
[19] Игорем Савельевым: https://twitter.com/leonspok
[20] Источник: https://habr.com/ru/post/442886/?utm_campaign=442886
Нажмите здесь для печати.