Generating multi-brand multi-platform icons with Sketch and a Node.js script — Part #1

First part: from the idea to the assets Sketch files.

TL;DR

Using a custom build script in Node JS, it is possible to manipulate a series of Sketch files, and then, using an internal Sketch tool, export their assets automatically, to generate multiple icon libraries, for multiple platforms and different brands, that support dynamic colorisation of the assets via design tokens, and AB testing of the assets via naming convention. Easy peasy :)


Well actually that’s not so easy, but certainly it can be done. This post is a detailed explanation of how we did it, and what we discovered along the way.

The problem we were trying to solve

At Badoo we build a dating app. Actually, multiple dating apps. For multiple platforms (iOS, Android, Mobile Web, Desktop Web), with multiple teams.

We use hundreds of icons in our apps. Some of them are the same across different apps, some are very specific to the brands of the apps. The icons are continuously evolving, in sync with the evolution of the design. Some new icons are added, some get updated, some become unused (often remaining in the codebase).

The icons are designed and maintained by our design team, and until now the only way for them to provide the correct assets to the different teams, platforms and apps, was to send them via email, chat or dropbox. This is not only a tedious work, but it’s absolutely error prone. And errors happened every time (we’re humans!): icons were updated on one platform but not on another; some icons were missing or in the wrong format or size, so there were back and forward between designers and developers; icons were exported directly from the Sketch file by the developers, and added to the codebase without checking if similar icons already existed (and could be reused). I am sure you know what I am talking about 😄

In Badoo we have a Design System, called Cosmos, and recently we have been able to introduce a library of Design Tokens across multiple platforms (Mobile Web, Android and iOS) for our main app and its white-labels. Essentially, we are now able to communicate design decisions (like the border of a button, the background color of a particular feature page, the font size of the Heading 1, the duration of the animation for a popup) in form of elementary design values, processed and delivered automatically to all the apps and platforms consuming these design tokens in their apps.

The way in which is now possible to transform a design idea, like a change of a color, to real code in production, with just a few clicks and in no time, really impressed product managers and designers.

So their next question (and request) was: can you do something similar for the assets? And our answer was: yes (probably) we can!

It was quite a leap of faith at that moment, I have to admit it. We had some ideas on how to do it, but we were not sure at all if it was technically doable, with all the boundary conditions we had. We agreed to start with an MVP project, but at the end everything went so well that it became our final product, with all the required features.

The requirements

The requirements for the MVP were very clear: create a pipeline that can take a Sketch file and export all the icons included in the file, in different formats, for different platforms, with the possibility of AB testing each icon.

The complexity here lies in the fact that, in our products, the same icon can have different colors and/or different shapes for different brands/white-labels (while the codebase of the app is the same, and so the name of the icon).

If you look at some of the icons used in our applications, you will see that some of them are identical, some are very similar apart from a few details, while some are quite different, in both their shapes and colors:

A comparison of the icon for the different brands

Now, the colors used in the icons are not any color, but are exactly the colors declared in the design tokens for that brand and these specific features:

A comparison of the colors for the different brands. The color values are specified as design tokens.

So, the result that we wanted to achieve with this new assets pipeline was not only to automate the process of generation and delivery of the icons, for all the different platforms and brands, but most of all to be able to “dynamically” colorise the icons depending on the brand/white-label.

Sketch and sketchtool

Sketch is the main design tool used by our design team. Even though we considered other options (Figma, mainly), there were no doubts that this was the format of the source files we were going to use for this project (because this is the tool that the designers are more proficient with, because that’s the format of the files where the existing icons/assets are used, and so on).

The fact was that, at the beginning of the project, we were not even sure what were the final formats expected by the platforms. In our mind the process was something very basic like this: we export the icons in SVG format from the Sketch file and then we consume the SVG files in Mobile Web and Android, and for iOS we find a library that can convert SVGs to PDFs. And that’s it. That was the plan when we started, but we didn’t know if it was going to work, and what were the unknown unknowns in this plan (hence, the MVP to find out if it was feasible, and how much was the effort).

I don’t know if you have ever worked with some “converter to PDF” but in my experience it’s always been a pain. Generally it “quite” works, but never at 100% as you need. So in the back of my mind, I had this feeling that this was a dangerous path.

Sketch has a way of exporting assets that is simply perfect, never had a problem with that (be it SVG, PDF, or other formats). So I started to search if there were other ways to interact with Sketch, to use its engine to export assets directly via Sketch, possibly in a programmatic way (I was considering also the possibility of building a custom plugin, but that would have required a lot of work for me, and I had zero experience in that field!).

I knew that Sketch internally is no more than a zip file (if you rename a .sketch file to .zip, double-click to uncompress it, and open the resulting folder, you will see a list of JSON files, and a bitmap used as preview):

The inner structure of a Sketch file, once uncompressed

So I started to explore the different JSON files, and try to understand the connections and dependencies between them.

I realised that somehow, despite the JSON files being deeply nested (and quite big!), the relations between the different entities inside their objects is not too complicated: you have pages, artboards and layers; inside the layers you have the paths, and you can have shared styles between them; each one of these entities has a unique ID that is used to keep a reference between the different files; and all the “page” objects are saved in JSON files, stored in a sub-folder called pages, with the ID of the page used as name of the file.

One important thing that I discovered, during this exploration, is that the name of the layers, pages, and styles are just labels, that can be changed at any time, without impacting the inner workings of the Sketch file: what matters is the unique ID assigned to them, and this is never exposed to the end user (but nonetheless can be read and referenced inside the JSON files). Here is an example of how the unique ID of a style looks like:

{
"_class": "sharedStyle",
"do_objectID": "49BA4E98-8D63-435C-81D9-E2F6CDB63136",
"name": "name-of/the-style",
"value": {
"_class": "style",
"endMarkerType": 0,
"fills": [
{
"_class": "fill",
"isEnabled": true,
"color": {
"_class": "color",
"alpha": 1,
"blue": 0.7176470588235294,
"green": 0.4627450980392159,
"red": 0
},
"fillType": 0,
"noiseIndex": 0,
"noiseIntensity": 0,
"patternFillType": 1,
"patternTileScale": 1
}
],
"miterLimit": 10,
"startMarkerType": 0,
"windingRule": 1
}
}

This sparkled an idea: that maybe we could use specific conventions on the names of the artboards and of the pages, to declare some kind of meta-information about the relations between the different assets, and use them programmatically at build time.

Sketchtool

At this point, after the initial explorations, the plan had changed from “let’s export the icons in SVG and then convert them” to “let’s build a plugin that allows us, within Sketch, to export directly the icons in the final format”. But still, the plan was very blurred (and the technical feasibility was still unsure).

It was while I was looking at existing plugins, in their source code, to see if and how they could interact with the export APIs of Sketch, that I came across a tool that I’ve never heard of: Sketchtool.

Sketchtool is an official Sketch tool (official as in: developed by Bohemian Coding), and according to the documentation:

… is a command line utility that’s bundled with Sketch, that allows you to perform some operations with Sketch documents, like inspecting them or exporting assets. It also lets you control Sketch from the command line to perform some actions (like running plugins, for example).

Wait… a command line utility to perform operations like exporting assets? That was what I was looking for! Also, it was an official tool, so no problems of versions, obsolescence, maintenance, etc.

I started immediately to look into it, and read the documentation. Which is not huge, just a single page on the Sketch website (I’ve not found many other resources or pages out there, about it: no surprise I’ve never heard of it :) )

Sketchtool is bundled directly with Sketch, and you can find it inside Sketch at this path: Sketch.app/Contents/Resources/sketchtool/.

If you launch is in your CLI with the command:
$ /Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool
this is the output you will see in your terminal (I have simplified it a little):

Usage: sketchtool <command> [<args>]
[--formats=<string>]
[--use-id-for-name{=YES|NO}]
[--export-page-as-fallback{=YES|NO}]
[--serial{=YES|NO}]
[--context=<string>]
[--application=<path>]
[--without-activating{=YES|NO}]
[--item=<string>]
[--items=<string>]
[--safemode{=YES|NO} | --no-safemode | -S {<YES|NO>}]
[--max-size=<float> | -m <float>]
[--background=<string> | -g <string>]
[--compression=<float> | -c <float>]
[--new-instance{=YES|NO}]
[--reveal{=YES|NO}]
[--timeout=<float>]
[--include-symbols{=YES|NO}]
[--bounds=<rectangle>]
[--outputJSON=<path>]
[--filename=<string>]
[--wait-for-exit{=YES|NO}]
[--scales=<path>]
[--overwriting{=YES|NO}]
[--group-contents-only{=YES|NO}]
[--trimmed{=YES|NO}]
[--help]
[--progressive{=YES|NO}]
[--save-for-web{=YES|NO}]
[--output=<path>]
Commands:
dump Dump out the structure of a document as JSON.
 export artboards  Export one or more artboards
export layers Export one or more layers
export pages Export an area from one or more pages
export preview Export a preview image for a document
export slices Export one or more slices
 help              Show this help message.
 list artboards    List information on the document's artboards.
list formats List the supported export formats.
list layers List information on all of the document's layers.
list pages List information on the document's pages.
list slices List information on the document's slices.
 metadata          List the metadata for a document.
run Run a command from a plugin, inside Sketch.
show Show the location of the various sketch folders.
See ‘sketchtool help <command>’ for more information on a specific command.

As you can see, the tool has four main usages: to read/dump the metadata of the internal JSON files; to list the entities inside a file; to export these entities; and to run a command exposed by a plugin. Also, each one of these commands has a lot of options available. In the case of the export command, almost all the options that you can find in the export panel are available also via Sketchtool’s command line:

The “export” panel in Sketch, with the available options

This means that it’s possible to use Sketch directly as export “engine” via sketchtool, without the need to use external converters (from SVG to PNG or PDF, for example). Big deal! 🎉

A quick test using sketchtool and a simple Sketch file with a few icons inside confirmed all the initial suppositions: just using this simple tool, we can avoid to use or build our own custom exporters: we can just rely on Sketch itself!

The Sketch file(s)

Once we knew that Sketch was the tool we were going to use, for both storing and exporting the icons, it was time to actually collect in a Sketch file the icons used in our applications.

Initially, we were supposed to work only on a limited set of icons, for the MVP project, but we quickly realised that it would have been better to have them collected all at once, to immediately spot duplicates, inconsistencies, missing icons, etc.

Our designers did an incredible job and in a couple of days a large part of the assets used in their Sketch files was collected and organised in a single file. At the end of this phase, the Sketch file looked like this:

An overview of the Sketch file containing the icons used in our application.

In the file, each icon has its own artboard, named with the desired name of the icon (it will be used by Sketch as file name when exporting the assets). 
All the paths are converted to outlines, and the combined paths (generally as union or subtraction) are flattened to a single shape. This ensures that the generated assets keep the perfect visual aspect as in the source file, and the best compatibility with the different platforms.

An example of how the icons (and their layers) are organised in the assets Sketch file. The light pink background applied to the artboards is used to have a visual contrast with the white areas.

Dynamic coloring the icons with Shared Styles (and Design Tokens)

Once we had the icons collected, the next step was to apply the right colors to them. What we did was to create a set of pre-defined Shared Styles in Sketch, with the names matching those of the design tokens used in our design system, and then use them to apply colors to the icons.

This is how a style is applied to a layer:

A layer in Sketch with a pre-defined style (fill color).

and this is how the styles are declared and then applied to an element:

How we have decided to organize the styles in Sketch

The naming convention is very important here: all the styles can be organised in any sub-folders by the designers, as soon as the name of the style itself matches the name of the corresponding design token for that color, so that later it can be referenced programmatically by the build script.

Pages and artboard names used for AB testing

At this point, it was time to understand how to allow the designers to do AB testing on the icons. Once again, at the end, we decided to rely on naming conventions (big fan of K.I.S.S. here).

In this case, we used the name of the pages to detect when a set of icons was an AB test/experiment (using “XP_” as prefix) and the name of the artboards to detect which asset the AB test was referring to, and its different variants (enclosed within square brackets).

How the naming convention on pages and artboards is used to “declare” an AB test. Notice: the icons are just a fake example I created for testing, they are not real icons used in a product :)

The names used for the tests and the variants are not generic names: they must match the unique-name-IDs assigned to the experiments/variants in our internal user-split tool. In this way, the assets can later be correctly associated with the correct AB test user group.

Multiple files for multiple brands

The last piece of the puzzle was: how do we support different shapes of the same icon, for different brands?

This was a very important requirement for the product managers, and we had different options in front of us. At first, we thought to use different pages in the same Sketch file, with different page names for the different brands; but soon we realised it would have increased a lot the complexity and the burden for the designers to keep the icons in sync between the different brands. So we decided that the solution was to have multiple files: a “common” file, where we stored all the icons that were identical for the different brands, and brand-specific files that contained only the icons that were overriding the “base” icons of the common file.

At this point the Sketch files were ready. We were ready to start writing code.