Scripting API
With the release of version 40 of Sketch, we’re slowly moving towards rolling out our Javascript API as a better way to work with the Sketch model from within your plugins.
As part of this work, we’ve updated this site to include an API Reference section.
The API itself is still under development, and should not be regarded as completely fixed or ready for prime-time.
It’s getting there however, and if you’re starting to work on new plugins, you may want to consider trying it out.
Over the next few releases we’ll be developing the API further, updating all of our existing plugin examples to use the API, and also adding some new examples.
NSAppTransportSecurity
With the next version of Sketch, we’re planning on fully adopting a change that Apple made a while ago known as App Transport Security. This relates to the security of HTTP connections, and requires that all such requests be made using https:
and not just plain http:
.
Until now we’ve disabled this feature, but soon we will enable it.
Unless your plugin requests content from web pages, it should be unaffected by this change.
If however you do fetch data or resources from web pages, you will need to change over to using https:
in your URLs.
We also suggest that everyone changes to using https:
for any URLs specified in the plugin’s manifest file. Future versions of Sketch may use these URLs.
We’ve been hard at work over the last few weeks here on some updates to the Plugin system.
Some of these will be rolling out along with the upcoming 3.8 release, which is now in beta. Others will come later, but in both cases, we wanted to give everyone in the developer community some early warning.
Deprecated APIs
As many of you are aware, we quite often have to change the code internally, which sometimes means that APIs which we’ve publicised become deprecated. Until now we’ve tended to just leave the old APIs in the code too, and have them spit out a console message saying that they are deprecated.
Moving forwards, we intend to start actually removing these APIs. We’ll give you a version or two’s notice (so if we deprecate something in 3.7, we won’t remove it until 3.9), but you should be aware that deprecated will no longer mean “you can ignore this” and will now mean “you really should stop using this”.
Legacy Plugins
On a similar note, back in version 3.3 we introduced a new bundle format for Plugins. It has lots of benefits, but to ease the transition, we continued to support old single-file Plugins.
In order to simplify our code and allow us to add new scripting features, we intend to deprecate and then remove support for old Plugins. Starting with version 3.9, any commands provided by old-style Plugins will be grouped under a “Legacy” heading in the menu.
Following on from that, a later version of Sketch will no longer load old Plugins.
We encourage you to move your Plugins over to the new format now! It’s a pretty simple job, and it will future-proof you.
We’re sure that you are completely sold on this change by now, but one more little nudge, just in case: if you want to have your Plugin featured on our website, you’ll need to change them to the new format!
Action API
With 3.8, we are introducing the much-requested ability for Plugins to be able to respond to actions that the user performs in Sketch.
We have posted some documentation and example Plugins for action support.
We want to make it clear at this point that this is version 1.0 of action support, and more will follow. We are aware that there are some inconsistencies with the way it works right now, and not all the things a user can do will be available to begin with. It’s also worth saying that some things may never be available as actions, for performance reasons.
Even having said that though, this feature should greatly expand the range of things that Plugins can usefully do, and we look forward to seeing what you do with it. Please send us feedback on how it works for you, and what you’d like to see change.
Scripting API
As a few of you may have noticed, we made a tentative step in 3.7 towards adding a JavaScript-only API which Plugins can call.
The intention with this is to make a smaller, more stable set of functionality available to Plugins directly from JavaScript. Each time we release a new version of Sketch we will try to ensure that the API continues to work. This should insulate Plugins using the API from the current situation where they are forced to use internal Sketch code, and then get broken when we change that code.
Version 3.8 does contain a slightly updated version of this, but we don’t consider it ready for prime-time, and it isn’t documented properly. The design of the API also definitely isn’t stable at this point, and we don’t recommend using it yet as the names of classes and methods are likely to change.
Consider this an early warning that it is coming, and again, please send us feedback on how you’d like to see it work.
Sketch is a great tool for humans, but sometimes (especially in larger teams), we want to let the robots get in on the action too.
Making apps, websites, even icons is all about iteration: make it, try it, debug it, refine it, rinse and repeat.
Whilst we can’t automate the creative stuff, there are some steps in each of these iterations that are just drudge work. Exporting assets at a particular set of sizes and formats is an obvious example here, but maybe you are also supposed to extract certain measurements each time, or fire off certain exports in an email, or submit them to source control, or perform some other simple jobs.
We’ve done our best to make things like exporting a pain-free process from within Sketch itself, but even so, these jobs are often ripe for a more complete form of automation.
Wouldn’t it be nice if you could write scripts that are able to parse sketch files, extracting data and exporting images then doing things with the results?
Wouldn’t it be even nicer if these scripts could run unattended, maybe in response to your document changing, perhaps on a server where Sketch isn’t installed?
In some cases, there may be existing chains of tools and scripts in place as part of your (or your company’s) workflow. Wouldn’t it be nice if you could integrate Sketch with these?
This is why we made sketchtool.
What It Isn’t
First, it’s probably sensible to say what sketchtool doesn’t do.
For now, at least, sketchtool cannot create new Sketch documents, nor can it modify existing ones.
We may support a broader range of features at a later date, but for now, sketchtool is all about extracting things from existing documents.
What It Is
Essentially sketchtool lets you do three categories of thing:
- get a list of pages, artboards, slices
- export pages, artboards, slices or arbitrary layers
- get a full description of the entire document
Listing
Given a sketch document called Test.sketch, you can list the pages in it like this:
sketchtool list pages Test.sketch
This will output a JSON record describing the name, id and bounds of each page.
Similarly
sketchtool list artboards Test.sketch
sketchtool list slices Test.sketch
does the same for the artboards and slices (and exported layers) in a document.
Exporting
Getting information from a document is all very well, but what you probably want is to export images.
To extract everything that has been marked for export in a document, you can do:
sketchtool export layers Test.sketch
Similarly:
sketchtool export pages Test.sketch
sketchtool export artboards Test.sketch
sketchtool export slices Test.sketch
You can also modify the output using various options.
The --output
option lets you specify the output folder for the export.
The --formats
option lets you specify a list of formats to use for the export (e.g. “svg,png”).
The --scales
option lets you specify the scales to export at (x1, x2 etc).
The --items
option lets you list just one or more items to export, by name or id.
Options such as --save-for-web
, --overwriting
, --compact
, --trimmed
, and --compression
also let you tailor the output.
Dumping Documents
One final option that is available currently with sketchtool is the dump command:
sketchtool dump Test.sketch
This will output a complete JSON description of the document.
This description is very detailed, and thus the output gets large, quickly. It also exposes quite a lot of internal detail about the underlying model, so it’s subject to change in the future, and if you use this command in scripts, you should be prepared for the possibility that they might break one day.
In many cases, if the information that you are trying to extract isn’t obtainable using the list command, your next best bet may be to export using SVG, and parse that, since it’s a stable format.
If that doesn’t work though, you may be able to use the dump command to obtain the information that you need.
Future
In the future, we may expand sketchtool to do more.
If you have a feature request, a bug report, or just a story to tell about the cool things that you’re doing with it, please do get in touch.
You can download the latest version of sketchtool here.
(For comments, I’m @samdeane on Twitter)
We have taken care of the overhead of KVO in the model and things like Undo are now hardcoded and ‘fast’. But let’s have a look at how we can work with undo, and circumvent it when we don’t need it.
Currently in Sketch, each setter method in the model registers its own reverse undo action. It’s a simple concept to understand and it works well for simple object graphs.
Performance is critical in Sketch, and some users create enormous drawings with 10K+ objects in the graph easily. When the graph gets that big, making larger modifications to the tree fast becomes really important.
One example is dragging a complex PDF document into Sketch; after parsing it we might have created a couple thousand new objects. Registering undo actions for all setters called during this process is unnecessary and a big performance overhead.
Fixing this is easy; we just wrap the entire import block in a disable-undo-registration block. But we’re still calling all those methods, copying original object values before the undo manger decides to toss them all away. Not good.
So how shall we fix this? Let’s add a flag to each model object along the lines of ‘shouldRegisterUndo’. But now we have an extra instance variable on every object. That is wasteful and prone to error; we have to keep all these properties in sync somehow.
Let’s try again. We make a static BOOL ‘shouldRegisterUndo’ on the root model object instead. Now we run into trouble if we want to do asynchronous importing on a background thread. So we use NSThread’s threadDictionary to hold these values. Yuck.
No, instead let’s have a setX and setPrimitiveX. The former calls the latter but does some extra stuff like undo registration. Importing and other large operations can call setPrimitiveX and everything works. Except that setters may do other things and if those go into setX, we have to remember for each property on each model object whether it has any other side-effects. Ugh.
The word side-effects is probably key here. We should avoid them as much as possible and have setters just be setters. Undo is a task for the controller, one layer level up in our lasagna stack. It’s important to keep our levels properly separated from each other, each with a clearly defined role.
How exactly we’re going to do that I don’t know yet, but at least it’s clear it doesn’t belong here. To be continued….
(For comments, I’m @pieteromvlee on Twitter)
In his previous post, Pieter mentioned that originally we were using KVO registration as a generic mechanism to support Undo in our model.
Essentially what happened here was that any model object registered to observe itself, and then used the change notifications that it received to record actions with the Undo manager.
The advantage of this KVO system was that it was nice and generic. It avoided us having to write repetitious code in every model class to do essentially the same thing.
The disadvantage was the other side of the same coin. By being generic, the code had to do more work than a custom setter would need to do, particular for properties with primitive values. Let’s take the example of a simple CGFloat property like cornerRadius.
A custom setCornerRadius: setter could simply register an invocation with the Undo manager, calling itself to set corner radius back to its old value - which it can read before it obliterates it with the new one. Job done.
A generic solution going through KVO has to deliver everything through the observeValueForKeyPath:ofObject:change:context: method. The old and/or new values have to be delivered in a dictionary, which for primitive types like CGFloat means that they also have to be boxed up as NSNumbers. Additionally, the same method serves as a point of entry for every change to every property, so the property name has to be delivered as a string (the key path). KVO also potentially calls all sorts of other associated methods, such as willChange/didChange, again adding overhead. Finally, the actual KVO registration has to be done at some point, which clearly does a bit more fiddling behind the scenes - a little bit more overhead.
Now all of this isn’t onerous most of the time, and when used in the way that it’s intended, KVO is a brilliant solution for a number of tasks.
Problems
Using it in the way we were though, we had some problems. These were at least partially design issues in our code, but their effect was to make KVO not the most efficient way to do Undo.
The biggest problem was that in our design, KVO registration was done too early, essentially when we created objects. This meant that creating a new cluster of objects to form a part of the graph - for example when loading a file from disk - was incurring all of this Undo related overhead, even though we were never going to need to “undo” the loading.
Similarly, when importing an SVG or PDF file, I found that the code that I was writing to create the layers and set up their properties was spending quite a lot of its time doing unnecessary work relating to the KVO/Undo system.
In addition, there are times in the normal usage of Sketch where changes are made to a lot of objects at once - for example if the user does Select All and then drags things around. In this situation there are a number of problems to contend with, but losing extra performance to KVO isn’t that helpful…
Finally, I should actually mention here that the KVO code did more than just Undo registration, it also kept track of which objects in the graph had changed - information that was required by our rendering once it became a bit more sophisticated and asynchronous (more of that in a later post, perhaps).
For various reasons, we couldn’t address every one of our design problems simultaneously, but we did need to improve the performance.
Solutions
The upshot of all of this is that we moved away from KVO as a generic solution, and back towards something a bit more hand-coded.
Rather than actually hand-coding every setter however, we decided to write a code generator to do the work for us - something like Mogenerator, but not tied to Core Data.
The basic idea here is to describe our model classes, and then have a generator create all the boilerplate code for us. This can tackle Undo registration, but also encoding, decoding, and anything else where we want to apply a generic solution across a range of model classes.
Not only does this give us a bit more performance (or a lot more, in some cases), it gives us some future-proofing.
We have essentially a large body of boilerplate code for each model object, but when we need to make a design change, all we have to do is edit our code templates and recompile.
This gives us the agility to work through some more fundamental design issues one at a time, safe in the knowledge that we aren’t going to have a horrible block of manual changes to make for each one.
This code generation tool is called Coma by the way, and it’s open source. Right now it’s pretty undocumented, and only for the brave. I’m planning a follow-up post on it though…
One More Thing…
One other thing that I think is worth saying on this topic, from a more conceptual or philosophical point of view.
Code grows and evolves over time, and particularly when you have a shipping product, sometimes you end up somewhere that you didn’t quite intend to be.
I’m far from convinced that things like Undo registration and change tracking should be in any way entangled into the model in the first place. There are many times that we might want to manipulate an object graph, and only some of those times require Undo registration.
This stuff feels like controller code to me (in the MVC sense), and I think that it should live in a separate layer. Code that needs Undo registration, or needs to know what has changed when some action occurs, should ask this “model manipulation” layer to perform model changes, rather than doing them directly.
There are a number of ways this could be implemented: KVC, proxy objects that stand in for the actual model objects, or some other custom protocol. The actual technical solution isn’t as important to me as the conceptual separation. The problem with the naive KVO approach, or with custom setters on the model classes themselves, is that there is no separation, and you actually have to code in special ways to turn off Undo registration when you don’t want it (which feels like doing things the wrong way round, and tends to involve nasty globals or non thread-safe hacks).
As I hinted above, this stuff is something that we’re still working through with Sketch. We have the code working the way it currently does as the result of lots of logical decisions that made perfect sense at the time, but now we want to change track slightly. This sort of thing happens all the time in a code base of any significance. We don’t have the luxury of disappearing into a darkened room and emerging a year or two later with a complete rewrite, and we are old enough (and hopefully wise enough) to know how that often ends.
Instead, what we’re doing is building up good suites of unit tests, and slowly whittling away at the design, whilst keeping the product shipping.
This can feel agonisingly slow at times, when the temptation is there to just dive in a rip everything apart. It’s the right way to do it though, and it can actually be immensely satisfying to watch the design and code slowly shift, oil-tanker like, onto its new heading…
(For comments, I’m @samdeane on Twitter)