Sketch plug-in development practice

sketch-plugin-boilerplate --Quickly create a sample development template of sketch plugins in the sidebar, and click star.

Sketch is a very popular UI design tool. In 2014, with the addition of Symbols function and open developer rights in Sketch V43, it attracted a large number of developers' attention.

At present, there are two hot topics in Sketch development: ① React component renders to sketch Initiated by the airbnb team, ② using skpm Build and develop the Sketch plug-in.

There are few and imperfect materials related to Sketch plug-in development. We can focus on official documents in the process of plug-in development, but some of them are old. JavaScript API is officially provided to access the internal Sketch API and Mac OS framework through CocoaScript bridge to develop plug-ins (Sketch 53-56 version JS API is exposed in native Mac OS and Sketch API Special environment The underlying API functions provided are somewhat weak, and more in-depth understanding is needed Objective-C , CocoaScript ,AppKit,Sketch-Headers.

Sketch plug-in structure

Sketch Plugin is a collection of one or more scripts, each script defining one or more commands. Sketch Plugin is a folder with a. sketchplugin extension that contains files and subfolders. Strictly speaking, Plugin is actually OS X package Use as OS X bundle.

Bundle s have a directory with a standardized hierarchy that holds executable code and the resources used by that code.

Plugin Bundle folder structure

Bundles contains a manifest.json file, one or more scripts files (including scripts written in CocoaScript or JavaScript). It implements the commands displayed in the Plugins menu, as well as any number of shared library scripts and resource files.

mrwalker.sketchplugin
  Contents/
    Sketch/
      manifest.json
      shared.js
      Select Circles.cocoascript
      Select Rectangles.cocoascript
    Resources/
      Screenshot.png
      Icon.png

The most critical file is the manifest.json file, which provides information about the plug-in.

Tips:

The Sketch plug-in package can use skpm Generated during the construction process, skpm provides the official plug-in template of Sketch:

💁 Tip: Any Github repo with a 'template' folder can be used as a custom template:

skpm create <project-name> --template=<username>/<repository>

Manifest

The manifest.json file provides information about the plug-in, such as the author, description, icon, where to get the latest updates, defined commands, call menu items, and metadata for resources.

{
  "name": "Select Shapes",
  "description": "Plugins to select and deselect shapes",
  "author": "Joe Bloggs",
  "homepage": "https://github.com/example/sketchplugins",
  "version": "1.0",
  "identifier": "com.example.sketch.shape-plugins",
  "appcast": "https://excellent.sketchplugin.com/excellent-plugin-appcast.xml",
  "compatibleVersion": "3",
  "bundleVersion": 1,
  "commands": [
    {
      "name": "All",
      "identifier": "all",
      "shortcut": "ctrl shift a",
      "script": "shared.js",
      "handler": "selectAll"
    },
    {
      "name": "Circles",
      "identifier": "circles",
      "script": "Select Circles.cocoascript"
    },
    {
      "name": "Rectangles",
      "identifier": "rectangles",
      "script": "Select Rectangles.cocoascript"
    }
  ],
  "menu": {
    "items": ["all", "circles", "rectangles"]
  }
}

Commands

Declare a set of command information, each of which exists as a Dictionary data structure.

  • Script: the script of the function that implements the command function
  • Handler: function name, which implements the function of the command. When the function is called by Sketch, the context context parameter is passed in. If handler is not specified, Sketch will call onRun function in corresponding script by default
  • Shortcut: shortcut for command
  • name: displayed in Sketch Plugin menu
  • identifier: unique identification, it is recommended to use com.xxxx.xxx format, not too long

Menu

The Sketch loading plug-in displays the command names in order in the menu bar according to the specified information.

After understanding the structure of sketch plug-in, let's take a look at the official API provided by sketch: Actions API and Javascript API.

Sketch Actions API

The Sketch Actions API is used to listen to user actions and trigger events, such as OpenDocumen (open document), CloseDocument (close document), Shutdown (close plug-in), TextChanged, etc. see the official website for details: https://developer.sketch.com/...

  • register Actions

The manifest.json file, configure the corresponding handlers.

Example: the onondocument handler is called when the OpenDocument event is triggered.

"commands" : [
  ...
  {
    "script" : "my-action-listener.js",
    "name" : "My Action Listener",
    "handlers" : {
      "actions": {
        "OpenDocument": "onOpenDocument"
      }
    },
    "identifier" : "my-action-listener-identifier"
  }
  ...
],

my-action-listener.js

export function onOpenDocument(context) {                
  context.actionContext.document.showMessage('Document Opened')
}
  • Action Context

When the Action event is triggered, context.actionContext will be passed to the corresponding handler. Note that some actions contain two states: begin and finish. For example, SelectionChanged. You need to subscribe to SelectionChanged.begin and SelectionChanged.finish respectively, otherwise the event will be triggered twice.

Sketch JS API

There are three ways to develop sketch plug-in: ① using CocoaScript script, ② using Javascript + CocoaScript mixed development mode, ③ using AppKit + Objective-C. Sketch official suggests using JavaScript API to write sketch plug-in, and official encapsulates a set for Sketch Native API JS API Currently, it does not cover all scenarios. If a richer underlying API is needed, it needs to be implemented in combination with CocoaScript. adopt JS API It is very convenient to operate documents, artboards, groups, layers, import and export in sketching. Compatibility may need to be considered, JS API The schematic diagram is as follows:

CocoaScript

CocoaScript Realize the bridging function from JavaScript runtime to Objective-C runtime. You can write JavaScript external script through the bridge to access the rich API functions at the bottom of internal Sketch API and Mac OS framework.

Tips:

Mocha The implementation provides a bridge from the JavaScript runtime to the Objective-C runtime, which is included in CocoaScript.

CocoaScript is based on Apple's JavaScript core, which is a JavaScript engine supporting Safari. To write code with CocoaScript is actually to write JavaScript. CocoaScript includes a bridge to access Apple's Cocoa framework from JavaScript.

Use JavaScript to call Objective-C syntax with CocoaScript:

  • Method call syntax with '.'
  • Objective-C property setting

    • Getter: object.name()
    • Setter: object.setName('Sketch'),object.name='sketch'
  • All parameters are in '()'
  • In Objective-C, the ':' (parameter and function name separator) is converted to '', and the last underscore is optional
  • Return value. JavaScript uses var/const/let to set the type

Note: for detailed Objective-C to JavaScript, please refer to Mocha document

Example:

// OC: interface valueforkey of msplugin: onlayer:
NSString * value = [command valueForKey:kAutoresizingMask onLayer:currentLayer];

// cocoascript:
const value = command.valueForKey_onLayer(kAutoresizingMask, currentLayer);

// oc:
const app = [NSApplication sharedApplication];
[app displayDialog:msg withTitle:title];

// cocoascript:
const app = NSApplication.sharedApplication();
app.displayDialog_withTitle(msg, title)

// oc:
const openPanel = [NSOpenPanel openPanel]
[openPanel setTitle: "Choose a location..."]
[openPanel setPrompt: "Export"];

// cocoascript:
const openPanel = NSOpenPanel.openPanel
openPanel.setTitle("Choose a location...")
openPanel.setPrompt("Export")

Objective-C Classes

The sketch plug-in system can fully access the internal structure of the application and the core framework in Mac OS. Sketch is built with Objective-C. its Objective-C class provides Javascript API calls through Bridge (CocoaScript/mocha). It is very helpful for us to develop plug-ins by simply understanding the related classes and class methods exposed by sketch.

Use some of the introspective methods defined by Bridge to access the following information:

String(context.document.class()) // MSDocument

const mocha = context.document.class().mocha()

mocha.properties() // array of MSDocument specific properties defined on a MSDocument instance
mocha.propertiesWithAncestors() // array of all the properties defined on a MSDocument instance

mocha.instanceMethods() // array of methods defined on a MSDocument instance
mocha.instanceMethodsWithAncestors()

mocha.classMethods() // array of methods defined on the MSDocument class
mocha.classMethodsWithAncestors()

mocha.protocols() // array of protocols the MSDocument class inherits from
mocha.protocolsWithAncestors()

Context

When you enter the command customized by the plug-in, Sketch will look for the implementation function corresponding to the modified command and pass in the context variable. Context contains the following variables:

  • command: MSPluginCommand Object, currently executing command
  • document: MSDocument Object, current document
  • plugin: MSPluginBundle Object, the current plug-in bundle, containing the currently running script
  • scriptPath: NSString the absolute path of the currently executed script
  • scriptURL: the absolute path of the currently executed script. Unlike scriptPath, it is an NSURL object
  • selection: an NSArray object that contains all the currently selected layers. Every element in the array is MSLayer object

Tip: MS leading class is called Sketch encapsulation class, such as layer base class MSLayer ), text layer base class MSTextLayer ), bitmap layer base class MSBitmapLayer , NS starts with the class contained in AppKit

const app = NSApplication.sharedApplication()

function initContext(context) {
        context.document.showMessage('Initial execution script')
        
    const doc = context.document
    const page = doc.currentPage()
    const artboards = page.artboards()
    const selectedArtboard = page.currentArtboard() // Currently selected palette
    
    const plugin = context.plugin
    const command = context.command
    const scriptPath = context.scriptPath
    const scriptURL = context.scriptURL
    const selection = context.selection // Selected layer
}

Sketch plug-in development

We have learned a lot about the development of Sketch plug-ins. Then we have two practical examples: ① creating auxiliary content panel window, ② side bar navigation. In order to facilitate development, we need to carry out the following operations before development:

Crash protection

When the Sketch run crashes, it deactivates all plug-ins to avoid a circular crash. For users, it is very cumbersome to manually enable the required plug-ins in the menu bar after each crash and restart. Therefore, you can disable this feature with the following command.

defaults write com.bohemiancoding.sketch3 disableAutomaticSafeMode true

Plug-in cache

To enable or disable caching by configuring:

defaults write com.bohemiancoding.sketch3 AlwaysReloadScript -bool YES

This method is not applicable to some scenarios, such as setting the coscript. Currentscript(). Setshouldkeeparound (true) block will remain resident in memory, so you need to release it through coscript.setShouldKeepAround(false).

WebView debugging

If the plug-in implementation scheme uses WebView as the interface, the debugging function can be enabled through the following configuration.

defaults write com.bohemiancoding.sketch3 WebKitDeveloperExtras -bool YES

Create auxiliary content panel window

First of all, we are familiar with the auxiliary content panel under Mac OS, as shown in the example of the leftmost NSPanel in the figure below. It is an auxiliary window with display area, settable style effect and operable buttons in the upper left corner.

To create the following content panel in Sketch, you need to use the NSPanel class in the AppKit framework under Mac OS. It is a subclass of NSWindow, which is used to create auxiliary windows. Content panel appearance style setting can be set through related properties of NSPanel class, or fuzzy background effect can be added through NSVisualEffectView class of AppKit. In the content area, you can use the wk webview class of AppKit to render the web page content display.

  • Create Panel
const panelWidth = 80;
const panelHeight = 240;

// Create the panel and set its appearance
const panel = NSPanel.alloc().init();
panel.setFrame_display(NSMakeRect(0, 0, panelWidth, panelHeight), true);
panel.setStyleMask(NSTexturedBackgroundWindowMask | NSTitledWindowMask | NSClosableWindowMask | NSFullSizeContentViewWindowMask);
panel.setBackgroundColor(NSColor.whiteColor());

// Set the panel's title and title bar appearance
panel.title = "";
panel.titlebarAppearsTransparent = true;

// Center and focus the panel
panel.center();
panel.makeKeyAndOrderFront(null);
panel.setLevel(NSFloatingWindowLevel);

// Make the plugin's code stick around (since it's a floating panel)
COScript.currentCOScript().setShouldKeepAround(true);

// Hide the Minimize and Zoom button
panel.standardWindowButton(NSWindowMiniaturizeButton).setHidden(true);
panel.standardWindowButton(NSWindowZoomButton).setHidden(true);
  • Panel add blurred background
// Create the blurred background
const vibrancy = NSVisualEffectView.alloc().initWithFrame(NSMakeRect(0, 0, panelWidth, panelHeight));
vibrancy.setAppearance(NSAppearance.appearanceNamed(NSAppearanceNameVibrantLight));
vibrancy.setBlendingMode(NSVisualEffectBlendingModeBehindWindow);

// Add it to the panel
panel.contentView().addSubview(vibrancy);
  • Panel insert webview rendering
  const wkwebviewConfig = WKWebViewConfiguration.alloc().init()
  const webView = WKWebView.alloc().initWithFrame_configuration(
    CGRectMake(0, 0, panelWidth, panelWidth),
    wkwebviewConfig
  )
  
  // Add it to the panel
  panel.contentView().addSubview(webView);
  
  // load file URL
  webview.loadFileURL_allowingReadAccessToURL(
    NSURL.URLWithString(url),
    NSURL.URLWithString('file:///')
  )

Sidebar navigation development

When we develop complex Sketch plug-ins, we usually need to develop sidebar navigation display plug-in function buttons and click to trigger related operations. For the development of sidebar navigation, we mainly use the classes in AppKit, including NSStackView, NSBox, NSImage, NSImageView, NSButton, etc. the core code is as follows:

  // create toolbar
  const toolbar = NSStackView.alloc().initWithFrame(NSMakeRect(0, 0, 40, 400))
  threadDictionary[SidePanelIdentifier] = toolbar
  toolbar.identifier = SidePanelIdentifier
  toolbar.setSpacing(8)
  toolbar.setFlipped(true)
  toolbar.setBackgroundColor(NSColor.windowBackgroundColor())
  toolbar.orientation = 1
    
  // add element
  toolbar.addView_inGravity(createImageView(NSMakeRect(0, 0, 40, 22), 'transparent', NSMakeSize(40, 22)), 1)
  const Logo = createImageView(NSMakeRect(0, 0, 40, 30), 'logo', NSMakeSize(40, 28))
  toolbar.addSubview(Logo)

  const contentView = context.document.documentWindow().contentView()
  const stageView = contentView.subviews().objectAtIndex(0)

  const views = stageView.subviews()
  const existId = views.find(d => ''.concat(d.identifier()) === identifier)

  const finalViews = []

  for (let i = 0; i < views.count(); i++) {
    const view = views[i]
    if (existId) {
      if (''.concat(view.identifier()) !== identifier) finalViews.push(view)
    } else {
      finalViews.push(view)
      if (''.concat(view.identifier()) === 'view_canvas') {
        finalViews.push(toolbar)
      }
    }
  }

    // add to main Window
  stageView.subviews = finalViews
  stageView.adjustSubviews()

See open source code for details: https://github.com/o2team/ske... (welcome to star exchange)

debugging

When the plug-in runs, Sketch creates a JavaScript context associated with it, which can be debugged using Safari.

In Safari, open developer > your machine name > automatically show web inspector for jscontexts, and enable the option Automatically Pause Connecting to JSContext. Otherwise, the inspector will close before it can interact (the context will be destroyed when the script is finished).

Now you can use breakpoints in your code, check the values of variables at run time, and so on.

Journal

JavaScriptCore Environment for running the Sketch plug-in There is also a way to debug JavaScript code to log. We can put a pile of console.log/console.error and so on in the key steps to view the log of the drop point.

There are several options for viewing logs:

  • Open Console.app and find the Sketch log
  • View ~ / Library/Logs/com.bohemiancoding.sketch3/Plugin Output.log file
  • Run the skpm log command, which can output the above files (execute the skpm log -f to output the logs in a streaming way)
  • Using plug-ins developed by skpm, install sketch-dev-tools , use console.log to log and view.

SketchTool

SketchTool is a CLI tool included in Sketch, through which you can perform related operations on Sketch documents:

The sketchtool binary is located in the Sketch application package:

Sketch.app/Contents/Resources/sketchtool/bin/sketchtool

Set alias:

alias sketchtool="/Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool"

Use:

sketchtool -h  # view help
sketchtool export artboards path/to/document.sketch  # Export Sketchpad
sketchtool dump path/to/document.sketch # Export Sketch document JSON data
sketchtool metadata path/to/document.sketch # View Sketch document metadata
sketchtool run [Plugin path] # Run plug-ins

Note: SketchTool requires OSX 10.11 or later.

Other Resources

sketch reference api

Github SketchAPI

Tags: Javascript JSON Mac github

Posted on Tue, 05 Nov 2019 19:11:11 -0800 by brad.techguy