sketch-plugin-boilerplate -- 快速建立側邊欄 sketch plugins 開發模版樣例, 點贊 star 。
Sketch 是很是流行的 UI 設計工具,2014年隨着 Sketch V43 版本增長 Symbols 功能、開放開發者權限,吸引了大批開發者的關注。javascript
目前 Sketch 開發有兩大熱門課題:① React 組件渲染成 sketch 由 airbnb 團隊發起,② 使用 skpm 構建開發 Sketch 插件。html
Sketch 插件開發相關資料較少且不太完善,咱們開發插件過程當中能夠重點參考官方文檔,只是有些陳舊。官方有提供 JavaScript API 藉助 CocoaScript bridge 訪問內部 Sketch API 和 macOS 框架進行開發插件(Sketch 53~56 版 JS API 在 native MacOS 和 Sketch API 暴露的特殊環境中運行),提供的底層 API 功能有些薄弱,更深刻的就須要瞭解掌握 Objective-C 、 CocoaScript 、AppKit、Sketch-Headers。java
Sketch Plugin 是一個或多個 scripts 的集合,每一個 script 定義一個或多個 commands。Sketch Plugin 是以 .sketchplugin
擴展名的文件夾,包含文件和子文件夾。嚴格來講,Plugin 其實是 OS X package,用做爲 OS X bundle。react
Bundle 具備標準化分層結構的目錄,其保存可執行代碼和該代碼使用的資源。git
Bundles 包含一個 manifest.json
文件,一個或多個 scripts 文件(包含用 CocoaScript 或 JavaScript 編寫的腳本),它實現了 Plugins 菜單中顯示的命令,以及任意數量的共享庫腳本和資源文件。github
mrwalker.sketchplugin Contents/ Sketch/ manifest.json shared.js Select Circles.cocoascript Select Rectangles.cocoascript Resources/ Screenshot.png Icon.png
最關鍵的文件是 manifest.json
文件,提供有關插件的信息。web
小貼士:json
Sketch 插件包可使用 skpm 在構建過程當中生成,skpm 提供 Sketch 官方插件模版:canvas
skpm/skpm
- The simplest possible plugin setup. (_default_)skpm/with-prettier
- A plugin setup featuring linting with ESLint and code formatting with Prettier.skpm/with-datasupplier
- A template to create DataSupplier plugins (check our blog for more info)skpm/with-webview
- A template to create plugins displaying some rich UI in a WebView (check sketch-module-web-view for more info)💁 Tip: Any Github repo with a 'template' folder can be used as a custom template:api
skpm create <project-name> --template=<username>/<repository>
manifest.json
文件提供有關插件的信息,例如做者,描述,圖標、從何處獲取最新更新、定義的命令 (commands) 、調用菜單項 (menu) 以及資源的元數據。
{ "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"] } }
聲明一組 command 的信息,每一個 command 以 Dictionary
數據結構形式存在。
context
上下文參數。若未指定 handler,Sketch 會默認調用對應 script 中 onRun
函數com.xxxx.xxx
格式,不要過長Sketch 加載插件會根據指定的信息,在菜單欄中有序顯示命令名。
在瞭解了 Sketch 插件結構以後,咱們再來了解一下,sketch提供的官方 API: Actions API, Javascript API。
Sketch Actions API 用於監聽用戶操做行爲而觸發事件,例如 OpenDocumen(打開文檔)、CloseDocument(關閉文檔)、Shutdown(關閉插件)、TextChanged(文本變化)等,具體詳見官網:https://developer.sketch.com/...
manifest.json 文件,配置相應 handlers。
示例:當 OpenDocument 事件被觸發時調用 onOpenDocument handler 。
"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.actionContext
傳遞給相應 handler
。注意有些 Action 包含兩個狀態begin
和 finish
,例如 SelectionChanged
,需分別訂閱 SelectionChanged.begin
和 SelectionChanged.finish
,不然會觸發兩次事件。
Sketch 插件開發大概有以下三種方式:① 純使用 CocoaScript 腳本進行開發,② 經過 Javascript + CocoaScript 的混合開發模式, ③ 經過 AppKit + Objective-C 進行開發。Sketch 官方建議使用 JavaScript API 編寫 Sketch 插件,且官方針對 Sketch Native API 封裝了一套 JS API,目前還未涵蓋全部場景, 若須要更豐富的底層 API 需結合 CocoaScript 進行實現。經過 JS API 能夠很方便的對 Sketch 中 Document
、Artboard
、Group
、Layer
進行相關操做以及導入導出等,可能須要考慮兼容性, JS API 原理圖以下:
CocoaScript 實現 JavaScript 運行環境到 Objective-C 運行時的橋接功能,可經過橋接器編寫 JavaScript 外部腳本訪問內部 Sketch API 和 macOS 框架底層豐富的 API 功能。
小貼士:Mocha 實現提供 JavaScript 運行環境到 Objective-C 運行時的橋接功能已包含在CocoaScript中。
CocoaScript 創建在 Apple 的 JavaScriptCore 之上,而 JavaScriptCore 是爲 Safari 提供支持的 JavaScript 引擎,使用 CocoaScript 編寫代碼實際上就是在編寫 JavaScript。CocoaScript 包括橋接器,能夠從 JavaScript 訪問 Apple 的 Cocoa 框架。
藉助 CocoaScript 使用 JavaScript 調 Objective-C 語法:
Objective-C 屬性設置
object.name()
object.setName('Sketch')
,object.name='sketch'
var/const/let
設置類型注意:詳細 Objective-C to JavaScript 請參考 Mocha 文檔
示例:
// oc: MSPlugin 的接口 valueForKey: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")
Sketch 插件系統能夠徹底訪問應用程序的內部結構和 macOS 中的核心框架。Sketch 是用 Objective-C 構建的,其 Objective-C 類經過 Bridge (CocoaScript/mocha) 提供 Javascript API 調用,簡單的瞭解 Sketch 暴露的相關類以及類方法,對咱們開發插件很是有幫助。
使用 Bridge 定義的一些內省方法來訪問如下信息:
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()
當輸入插件定製的命令時,Sketch 會去尋找改命令對應的實現函數, 並傳入 context
變量。context
包含如下變量:
MSPluginCommand
對象,當前執行命令MSDocument
對象 ,當前文檔MSPluginBundle
對象,當前的插件 bundle,包含當前運行的腳本NSString
當前執行腳本的絕對路徑NSURL
對象NSArray
對象,包含了當前選擇的全部圖層。數組中的每個元素都是 MSLayer
對象小貼士:MS 打頭類名爲 Sketch 封裝類如圖層基類 MSLayer)、文本層基類 MSTextLayer) 、位圖層基類 MSBitmapLayer,NS 打頭爲 AppKit 中含有的類
const app = NSApplication.sharedApplication() function initContext(context) { context.document.showMessage('初始執行腳本') const doc = context.document const page = doc.currentPage() const artboards = page.artboards() const selectedArtboard = page.currentArtboard() // 當前被選擇的畫板 const plugin = context.plugin const command = context.command const scriptPath = context.scriptPath const scriptURL = context.scriptURL const selection = context.selection // 被選擇的圖層 }
前面咱們瞭解了許多 Sketch 插件開發知識,那接下來實際上手兩個小例子: ① 建立輔助內容面板窗口, ② 側邊欄導航。爲了方便開發,咱們在開發前需先進行以下操做:
崩潰保護
當 Sketch 運行發生崩潰,它會停用全部插件以免循環崩潰。對於使用者,每次崩潰重啓後手動在菜單欄啓用所需插件很是繁瑣。所以能夠經過以下命令禁用該特性。
defaults write com.bohemiancoding.sketch3 disableAutomaticSafeMode true
插件緩存
經過配置啓用或禁用緩存機制:
defaults write com.bohemiancoding.sketch3 AlwaysReloadScript -bool YES
該方法對於某些場景並不適用,如設置 COScript.currentCOScript().setShouldKeepAround(true)
區塊會保持常駐在內存,那麼則須要經過 coscript.setShouldKeepAround(false)
進行釋放。
WebView 調試
若是插件實現方案使用 WebView 作界面,可經過如下配置開啓調試功能。
defaults write com.bohemiancoding.sketch3 WebKitDeveloperExtras -bool YES
首先咱們先熟悉一下 macOS 下的輔助內容面板, 以下圖最左側 NSPanel 樣例, 它是有展現區域,可設置樣式效果,左上角有可操做按鈕的輔助窗口。
Sketch 中要建立以下內容面板,須要使用 macOS 下 AppKit
框架中 NSPanel
類,它是 NSWindow
的子類,用於建立輔助窗口。內容面板外觀樣式設置,可經過 NSPanel
類相關屬性進行設置, 也可經過 AppKit
的NSVisualEffectView
類添加模糊的背景效果。內容區域則可經過 AppKit
的 WKWebView
類,單開 webview
渲染網頁內容展現。
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);
// 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);
webview
渲染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:///') )
咱們開發複雜的 Sketch 插件,通常都要開發側邊欄導航展現插件功能按鈕,點擊觸發相關操做。那開發側邊欄導航,咱們主要使用 AppKit
中的那些類呢,有 NSStackView
、 NSBox
、NSImage
、 NSImageView
、NSButton
等,大體核心代碼以下:
// 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()
詳細見開源代碼: https://github.com/o2team/ske... (歡迎 star 交流)
當插件運行時,Sketch 將會建立一個與其關聯的 JavaScript 上下文,可使用 Safari 來調試該上下文。
在 Safari 中, 打開 Developer
> 你的機器名稱 > Automatically Show Web Inspector for JSContexts
,同時啓用選項 Automatically Pause Connecting to JSContext
,不然檢查器將在能夠交互以前關閉(當腳本運行完時上下文會被銷燬)。
如今就能夠在代碼中使用斷點了,也能夠在運行時檢查變量的值等等。
JavaScriptCore 運行 Sketch 插件的環境 也有提供相似調試 JavaScript 代碼打 log 的方式,咱們能夠在關鍵步驟處放入一堆 console.log/console.error
等進行落點日誌查看。
有如下幾種選擇能夠查看日誌:
~/Library/Logs/com.bohemiancoding.sketch3/Plugin Output.log
文件skpm log
命令,該命令能夠輸出上面的文件(執行 skpm log -f
能夠流式地輸出日誌)console.log
打日誌查看。
SketchTool 包含在 Sketch 中的 CLI 工具,經過 SketchTool 可對 Sketch 文檔執行相關操做:
sketchtool 二進制文件位於 Sketch 應用程序包中:
Sketch.app/Contents/Resources/sketchtool/bin/sketchtool
設置 alias
:
alias sketchtool="/Applications/Sketch.app/Contents/Resources/sketchtool/bin/sketchtool"
使用:
sketchtool -h # 查看幫助 sketchtool export artboards path/to/document.sketch # 導出畫板 sketchtool dump path/to/document.sketch # 導出 Sketch 文檔 JSON data sketchtool metadata path/to/document.sketch # 查看 Sketch 文檔元數據 sketchtool run [Plugin path] # 運行插件
注意
:SketchTool 須要 OSX 10.11或更高版本。