| A consistent experience is a better experience.——Mark Eberman | 一致的體驗是更好的體驗。——Mark Eberman 《摘自設計師的16句名言》css
積木(Tangram)Sketch插件源於美團外賣UI的一致性項目,該項目自2019年5月份被提出,是UI設計團隊與研發團隊共建的項目,目的是改善用戶端體驗的一致性,提高多技術方案間組件的通用性和複用率,總體下降視覺改版的研發成本。html
一直以來,外賣業務都處於高速發展階段,人員規模在不斷擴大,項目複雜度在持續增長。目前平臺承載了美團餐飲、商超、閃購、跑腿、藥品等多個業務品類,用戶入口也覆蓋了美團App外賣頻道、外賣App、大衆點評等多個獨立應用。由於客戶端一直比較側重業務開發,爲了知足業務快速上線的需求,UI組件並無統一的實現,而是分散到各個業務場景中,在開發過程當中因UI缺少同一的標準而致使如下問題不斷凸顯:前端
UI/UE層面react
① UI缺少標準化的設計規範,在不一樣App及不一樣語言平臺上設計風格不統一,用戶體驗不一致。webpack
② 設計資源與代碼均缺少統一的管理手段,沒法實現積累沉澱,沒法適應新業務的開發需求。git
RD層面程序員
① 組件代碼實現碎片化,存在屢次開發的狀況,質量難以獲得保證。github
② 各端代碼API不統一,維護拓展成本較高,變動主題、適配Dark Mode等需求難以實現。web
QA層面npm
重複走查,頻繁迴歸,每次發版均需驗證組件質量。
PM層面
版本迭代效率低,版本需求吞吐量低,不能知足業務的快速拓展能力。
基於上述開發工做中的切實痛點,以及將來可預見的對客戶端能力的開發需求,咱們迫切須要一套統一的UI設計規範,以此沉澱出設計風格,創建統一的UI設計標準,從而抽離成熟的業務場景,提供高質量、可擴展、可統一配置的同時能基於Android/iOS/MRN/Mach組件開發的代碼庫,且具有支持多業務高層次的代碼複用能力,提升UI業務的中臺能力,使項目具備高度一致性。
咱們經過積木Sketch插件來落地設計規範,能夠保證設計元素均從既定設計標準中獲取,產出符合業務設計語言的設計稿,而各平臺UI組件庫中也有對應實現,從而使積木插件成爲UI一致性的抓手,最終能夠減小開發成本,提高交付質量,服務好咱們美團的多個業務團隊。
要想保持UI一致性,就不能打破規則。從設計階段顏色的選擇、字體的規範、控件的樣式到RD開發階段代碼的統一管理、API的制定、多端的實現方式,都必須遵照一套規則,而Sketch Plugin建設則是讓規範落地執行的解決方案。
在討論其重要性以前,咱們首先簡單介紹一下Sketch:Sketch是一個設計工具包,由總部位於荷蘭海牙的BohemianCoding團隊開發,該團隊成員目前不足百人,來自全球多個國家,經過互聯網遠程協做開發,屬於典型的高效開發團隊。
Sketch容易理解且上手簡單;可與團隊中的每一個人建立、更新和共享全部Symbol組件,實現設計資源的共享和版本管理,今後告別「final-final-final-1」;其版本迭代速度很是快,且能不斷添加新功能,知足用戶的需求,更符合互聯網時代;Sketch可使用真實數據進行設計。目前,咱們設計團隊已經全面使用Sketch進行設計。
設計語言包括Iconfont、色板、文字規範、話術、插畫、動畫、組件等。其實它並非一個抽象的概念,好比你們提到「美團」就會想起「美團黃」,想到可愛的「袋鼠」,想到那些騎着摩托車、穿着印有「美團外賣」亮黃色衣服的騎手小哥。經過設計語言,咱們能夠更好地傳達品牌主張和設計理念。UI團隊逐步將設計語言沉澱爲設計規範,並將其量化內置於積木Sketch Plugin中,使產出的設計稿和RD代碼庫中的組件一一對應,從而造成一個完整的閉環,進而可加速整個業務的交付流程。
其實,市面上已存在相似插件,爲何咱們還要本身動手開發呢?由於UI設計語言與自身業務關聯性很強,不一樣業務的色彩系統、圖形、柵格系統、投影系統、圖文關係千差萬別,其中任意一環的缺失都會致使一致性被破壞。現有插件所提供的通用設計元素沒法知足外賣設計團隊的需求,開發一款能夠與業務強關聯且功能可定製的插件,顯得尤其重要。
此外,統一的品牌符號、品牌特徵,也有助於加深產品在用戶心中的印象,統一的顏色和交互形式能幫助用戶加深對產品的熟悉感和信任感,一個好的設計語言自己能夠在體驗上爲產品加分,也可以更好創造一致性的體驗。
積木Sketch插件通過一段時間的建設,目前已具有Iconfont、標準色板、組件庫、數據填充、文字模板等功能。
咱們經過Iconfont能夠從美團的圖標庫中拉取設計團隊上傳的SVG圖標,並直接應用於設計稿;標準色板能夠限定設計師的顏色使用範圍,確保設計稿中的顏色均符合設計規範;組件庫中包含從外賣業務中抽離的基本控件與通用組件,具備可複用和標準化的特色,並與不一樣語言平臺組件庫中的代碼一一對應,使用組件庫中的組件進行設計,能夠提高UI的設計效率、開發效率以及走查效率;數據填充庫能夠實現圖片填充和文本填充,圖片包含了商品及商家素材,文字則包含了菜品、商鋪名等信息,經過數據填充可使設計師採用真實數據進行填充,讓設計稿更爲直觀,也更貼近線上環境;文字模板中內置了Head、SubTitle、Body、Caption的使用規範,根據設計稿中文字的位置,點擊文字圖層便可直接應用字體、行高、字距等屬性。
此外,咱們還根據設計同窗的使用反饋,不斷增添新功能。同時也在拓展插件的使用場景,增長業務線切換功能,使積木插件能夠爲更多的團隊服務,並期待它能成爲更多設計師的「貼心搭檔」。
相信你讀完上面的內容,確定火燒眉毛的想了解一下Sketch插件,以此迅速提高本身團隊開發效率了吧?
其實在開始以前,咱們可先了解一些不利的條件。第一點,因爲Sketch更新速度極快,可是官方文檔卻十分簡單且陳舊,所以不少知名的Sketch Plugin因每次API的變動過大紛紛放棄維護;第二點,因爲開發技術棧混亂,成熟項目通常還未開源,而開源的項目基本上沒有什麼參考價值,絕大多數都是「update 3 years ago」;最後一點,macOS開發資料更是少的可憐。
咱們閱讀了大量的文檔卻沒有理清頭緒,彷彿不少Wiki講到關鍵地方,好比某個很是期待的功能是怎麼實現的時候,做者居然一筆帶過,讓人摸不到頭腦。知乎上一篇Sketch Plugin的科普文,不少網友會評論「求教學視頻,我能夠花錢買的」。通過一步步踩坑,咱們就總結了一些開發經驗,爲了不你們「重複踩坑」,晚上能夠早點下班陪陪家人,咱們決定寫一篇文章記錄下開發的過程。雖然比起那些已經更新多版的成熟項目,但還有很多的差距,至少可讓你們再也不那麼迷茫。
固然,即便你以爲本身是個「跟Sketch八竿子打不着」的開發同窗,咱們也以爲這篇文章一樣也值得閱讀,由於你會經過本文接觸到前端、移動端、桌面端、服務端的各類開發知識。咱們都知道,愈來愈多的公司開始喜歡招全棧工程師,像Facebook基本上只招全棧工程師。你內心是否是在想:「是否是在搞笑啊?不過一個插件而已?」先別輕易下結論。
準備好了嗎?盤它!
好,先彆着急敲擊鍵盤。畢竟咱們連使用哪一種語言去開發都沒決定,這曾經也是困惱咱們許久的一個問題。目前Sketch Plugin開發大概有兩種方式:
① 使用JavaScript + CocoaScript的混合開發模式,Sketch團隊官方維護了一套JS API,並在開發者官網寫了一句很是振奮人心的話:「 Take advantage of ES6, access macOS frameworks and use the Sketch APIs without learning Objective-C or Swift.」
理想很美滿,但現實很骨感。這個API目前還不算完善,不少功能沒法實現,所以咱們須要搭配CocoaScript訪問更豐富的內部API。
② 直接採用Objective-C 或Swift,並搭配macOS的UI框架AppKit進行開發,簡單粗暴,而且能夠利用OC運行時直接調用Sketch內部API。但這裏要特別提醒一下,你要承擔的風險是:隨着Sketch的不斷更新,內部API的命名和使用方式可能會發生較大變化,不少知名插件都所以放棄更新。
本文采用了「混合開發模式」進行講解,但願可以給你一些小啓發。
Skpm(Sketch Plugin Manager)是Sketch提供的用於Plugin建立、Build以及發佈的官方工具。Skpm採用Webpack做爲打包工具,固然若是你對前端知識足夠熟悉,也能夠採用Rollup或者roadhog。可是,爲了防止遇到各類各樣的報錯,這裏並不建議你這麼作。
Skpm提供了一系列幫助快速入門的模板,最有用的莫過於skpm/with-webview,它能夠幫助咱們建立一個基於WebView展現的Demo示例,並且Skpm會在構建完成後,自動建立一個Symbolic Link將插件添加到Sketch的安裝目錄,使Plugin當即可用。
//基於webpack的Sketch官方打包工具skpm
npm install -g skpm
//建立示例工程
skpm create my-plugin --template=skpm/with-webview
//Install the dependencies
npm install
//構建插件
npm run build
複製代碼
Plugin Bundle 按照上面的步驟操做完成後,咱們會獲得以下插件目錄,它以標準化的分層結構存儲了源碼文件以及構建生成的Sketch插件安裝包。這裏沒有使用官方文檔中最簡單的Demo,而是使用目前開發中最爲經常使用的With-Webview模板進行分析,以避免出現學完「1+1」後遇到的全是「微積分」問題,而且大部分插件均是在此基礎上進行拓展。
目錄中的參數,相信你在看完註釋後立刻就能明白。但是若是此前沒有前端開發經驗,可能不瞭解在通過Webpack打包後,腳本文件的文件名會發生變動,好比resources中的webview.js通過打包後會儲存在插件的Resources文件夾中,而文件名則變動爲resources_webview.js,所以在進行代碼編寫時,若是須要在html中引用此文件,也要使用打包後的文件名,即:。這裏有個小技巧,若是你不知道腳本文件打包後的文件名及路徑,建議先使用Webpack進行編譯,而後查看其在打包後的Plugin中的位置和名稱,而後再進行引用。
├── assets //資源文件夾,如需更改需在package.json中的skpm.assets中設置
├── my-plugin.sketchplugin //skpm構建過程生成的插件包
│ └── Contents
│ ├── Resources
│ │ └── _webpack_resources
│ │ └── resources_webview.js
│ │ └── resources_webview.js.map
│ └── Sketch
│ ├── manifest.json
│ ├── __my-command.js
│ └── __my-command.js.map
├── package.json
├── webpack.skpm.config.js
├── resources //資源文件
│ ├── style.css
│ ├── webview.html
│ └── webview.js
└── src //須要被webpack打包的腳本文件以及manifest清單文件
├── manifest.json
└── my-command.js
複製代碼
Manifest
你沒有看錯!plugin中也有manifest.json,它與其它平臺好比Android開發中的清單文件意義相同。清單文件記錄了做者信息、描述、圖標以及獲取更新的途徑等等。想一想看,天天熬夜加班寫代碼,總得有個地方把你的名字記錄下來吧。但manifest最重要的做用實際上是告訴Sketch如何運行插件,以及如何將插件集成進Sketch的菜單欄中。
commands使用一個數組,記錄了插件所提供的全部命令。好比下面的例子,當用戶從菜單欄點擊 「顯示工具欄」這個條目時,就會執行script.js中的function showPlugin() 。menu則提供了插件在Sketch菜單欄中的佈局信息,Sketch會在插件被加載時初始化菜單。
{
"commands": [
{
"name": "顯示工具欄",
"identifier": "roo-sketch-plugin.toolbar",
"script": "./script.js",
"handlers": {
"run": "showPlugin"
}
}
],
"menu": {
"title": "🦘外賣積木SketchPlugin工具欄",
"items": ["roo-sketch-plugin.toolbar"]
}
}
複製代碼
package.json
簡單來講,只要你的項目中用到了NPM,根目錄下就會自動生成package.json文件。Node.js項目遵循模塊化的架構,package.json定義了這個項目所須要的各類模塊以及配置信息。使用npm install命令會根據這個配置文件,自動下載所需的模塊,也就是配置項目所需的運行和開發環境。
很是值得稱讚的是,Plugin開發中對於網絡請求、 I/O 操做以及其它功能,可使用與Node.js兼容的polyfill,其中許多經常使用modules已經預裝到了Sketch中,好比console、fetch、process、querystring、stream、util等。
這裏你只須要知道如下幾點:
{
"name": "roo-sketch-plugin",
"author": "hanyang",
"description": "外賣積木Sketch plugin,UI同窗好喜歡~",
"version": "0.1.0",
"skpm": {
"manifest": "src/manifest.json",
"main": "roo-sketch-plugin.sketchplugin",
"assets": ["assets/**/*"]
},
"resources": [
"src/webview/template/webview.js"
],
"scripts": {
"build": "rm -rf roo-sketch-plugin.sketchplugin && NODE_ENV=development skpm-build",
},
"dependencies": {},
"devDependencies": {}
}
複製代碼
Javascript API
因爲使用了與Safari相同的JS引擎,Plugin腳本能夠得到完整ES6支持。官方的JavaScript API由Sketch團隊維護,並容許訪問和修改Sketch文檔,經過API能夠向Sketch用戶提供數據並提供一些基本的用戶界面集成。
//訪問、修改和建立文檔從color到layer再到symbol等方方面面
var sketchDom = require('sketch/dom')
//對於異步操做,JavaScript API提供了fibers延長contex的lifeTime
var async = require('sketch/async')
//直接在Sketch中提供圖像或文本數據,DataSupplier直接與Sketch用戶界面集成。
var DataSupplier = require('sketch/data-supplier')
//無需從新build的狀況下顯示通知以及獲取用戶輸入
var UI = require('sketch/ui')
//保存圖層或文檔的自定義數據,並存儲插件的用戶設置。
var Settings = require('sketch/settings')
複製代碼
CocoaScript Syntax
CocoaScript經過賦予了JavaScript調用Sketch內部API以及macOS Cocoa frameworks的能力,這意味着除了標準的JavaScript庫外,還可使用許多很棒的類與函數。CocoaScript創建在蘋果的JavaScriptCore之上,而JavaScriptCore是爲Safari提供支持的JavaScript引擎。
所以,當你使用CocoaScript編寫代碼的時候,你就是在寫JavaScript。CocoaScript中的Mocha實現JS到Objective-C的Bridge,雖然Mocha包含在CocoaScript中,但文檔仍保留在原始Github中。所以,你在CocoaScript的Readme中看不到任何語法教程。這裏一個訣竅是,若是你想了解Mocha將原生的Sketch Objects經過bridge,從Objective-C傳遞到JavaScript層的屬性、類或者實例方法的信息,能夠將其經過console打印出來:
let mocha = context.document.class().mocha()
console.log(mocha.properties())
//OC
[executeOperation:withObject:error:]
//CocoaScript
executeOperation_withObject_error()
複製代碼
經過CocoaScript 提供的Bridge使用JavaScript調用Objective-C的基本語法以下:
行爲定義
Action指的是因爲用戶交互而在應用程序中發生的事件,好比「打開文檔」、「關閉文檔」、「保存」等。Sketch所提供的了Action API可使插件對應用程序中的事件作出反應,有點相似Android開發中的的BroadCast或者Job Scheduler。官方文檔列舉了數百個可供監聽的Action,但最經常使用到的只有下面幾個:
監聽回調
咱們只需在插件的manifest.json文件中添加一個handler便可。好比下面的例子添加了對於「OpenDocument」的監聽,也就是告訴插件在新文檔被打開時要去執行onOpenDocument這個function。
{
"script": "action.js",
"identifier": "my-action-listener-identifier",
"handlers": {
"actions": {
"OpenDocument": "onOpenDocument"
}
}
}
複製代碼
當一個Action被觸發時,會回調JS中的監聽方法,與此同時Sketch能夠向目標函數發送Action Context,其中包含動做自己的一些信息。在下面例子中,每次打開文檔時都會彈出一個Toast。
function onOpenDocument(context) {
context.actionContext.document.showMessage('Document Opened')
}
複製代碼
在常規的插件開發中,UI層通常採用Webview實現,所以你可使用各類前端開發框架,好比React或者Vue等;而插件的邏輯層(負責調用Skecth API)顯然不在WebView中,所以須要經過Bridge進行通訊。邏輯層將從服務器獲取到的數據傳遞給UI層展現,而UI層則將用戶的操做反饋傳遞給邏輯層,使其調用Sketch API更新Layers。
插件發送消息到WebView
//On the plugin:
browserWindow.webContents
.executeJavaScript('someGlobalFunctionDefinedInTheWebview("hello")')
.then(res => {
// do something with the result
})
//On the WebView:
window.someGlobalFunctionDefinedInTheWebview = function(arg) {
console.log(arg)
}
複製代碼
WebView發送消息給插件
//On the webview:
window.postMessage('nativeLog', 'Called from the webview')
//On the plugin:
var sketch = require('sketch')
browserWindow.webContents.on('nativeLog', function(s) {
sketch.UI.message(s)
})
複製代碼
通過了以上步驟,咱們就獲得了一個基礎插件,它以WebView做爲內容載體,並具備雙向通訊功能。打開插件時,Webview會將頁面加載完成的事件傳遞給邏輯層,邏輯層調用Sketch API彈出Toast;點擊Get a random number能夠從邏輯層獲取一個隨機數。
相信閱讀完上面的部分,製做一個簡單的插件對於你來講,已經有點「遊刃有餘」了。但這個時候,疑惑也隨之而來,爲何Demo和咱們經常使用插件的UI差異如此之大?
沒錯,官方文檔只教給咱們最基礎的插件開發流程,一個成熟的商業項目毫不僅僅是以上這些。一個功能完善的插件應該包括如下三部分:工具欄、WebView容器以及業務數據。下面,咱們會一步步爲你展現如何開發一個商業化插件UI,同時也會演示美團外賣「填充功能」的實現(注:篇幅緣由文檔中僅保留關鍵代碼。)
所謂吸附式工具欄,就是展現在Skecth右側Inspector Panel旁邊的工具欄,它以吸附的方式與Sketch操做界面融爲一體,這也是絕大多數插件的視覺呈現方式。工具欄中展現了當前插件能夠提供的大部分功能,方便咱們在操做Document時快速選取使用。
開發工具欄主要使用NSStackView、NSButton、NSImage以及NSFont這幾個類,若是沒有開發過macOS應用的同窗可能對這些類有些陌生,能夠類比iOS開發中以UI做爲前綴的控件類,NS前綴主要是AppKit以及Foundation的相關類,MS前綴則是Skecth的相關類,CA、CF前綴爲核心動畫庫和核心基礎類。
下面的代碼記錄了建立工具欄的關鍵步驟,更爲詳細的操做能夠參考一些Github倉庫,好比sketch-plugin-boilerplate等。
const contentView = context.document.documentWindow().contentView();
const stageView = contentView.subviews().objectAtIndex(0);
//1.建立toolbar
const toolbar = NSStackView.alloc().initWithFrame(NSMakeRect(0, 0, 27, 420));
toolbar.setBackgroundColor(NSColor.windowBackgroundColor());
toolbar.orientation = 1;
//2.建立Button
const button = NSButton.alloc().initWithFrame(rect)
const Image = NSImage.alloc().initWithContentsOfURL(imageURL)
button.setImage(image)
button.setTitle("數據填充")
button.setFont(NSFont.fontWithName_size('Arial',11))
//3.將Button加入toolbar
toolbar.addView_inGravity(button, gravityType);
//4.將toolbar加入SketchWindow
const views = stageView.subviews()
const finalViews = []
for (let i = 0; i < views.count(); i++) {
finalViews.push(view)
if(view[i].identifier() === 'view_canvas'){
finalViews.push(toolbar)
}
stageView.subviews = finalViews
stageView.adjustSubviews()
複製代碼
除了經過CocoaScript建立原生NSPanel外,這裏推薦使用官方的sketch-module-web-view快速建立WebView容器,它提供了豐富的API對窗口的展現樣式和行爲進行定製,包括Frameless Window、Drag等,同時還封裝了WebView與插件層的通訊的Bridge,使你能夠輕鬆在"frontend" (the WebView)和"backend" (the plugin running in Sketch)之間發送消息。
//(1)方法一:原生方式加入webview
const panel = NSPanel.alloc().init();
panel.setFrame_display(NSMakeRect(0, 0, panelWidth, panelHeight), true);
const wkwebviewConfig = WKWebViewConfiguration.alloc().init()
const webView = WKWebView.alloc().initWithFrame_configuration(
CGRectMake(0, 0, panelWidth, panelWidth),
wkwebviewConfig
)
panel.contentView().addSubview(webView);
webview.loadFileURL_allowingReadAccessToURL(
NSURL.URLWithString(url),
NSURL.URLWithString('file:///')
)
//(2)方法二:使用官方的BrowserWindow
import BrowserWindow from "sketch-module-web-view";
const browserWindow = new BrowserWindow(options);
const webViewContents = browserWindow.webContents;
webViewContents
.executeJavaScript(`someGlobalFunctionDefinedInTheWebview(${JSON.stringify(someObject)})`)
.then(res => {
// do something with the result
})
browserWindow.loadURL(require('./webview.html'))
複製代碼
歷盡千辛萬苦,咱們終於拿到了WebView,這下就能夠發揮你「天馬行空」的想象力了。無論是React仍是Vue,亦或只是一些簡單的靜態頁面對於你而言應該都不在話下。在完成界面開發後,只需經過Window向插件發送指令便可。下面的例子演示了積木插件的「數據填充」功能。
UI側
import React from 'react';
import ReactDOM from 'react-dom';
//使用react搭建用戶頁面
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
//傳遞用戶點擊填充類目給插件層,這裏以填充文字爲例
export const PostMessage = (name, fillData) => {
try {
window.postMessage("fill-text-layer", fillData);
} catch (e) {
console.error(name, "出現異常!!!" + fillData);
}
};
複製代碼
插件側
browserWindow.webContents.on('fill-text-layer', function(s) {
//找到當前頁面document
const document = context.document;
//獲取用戶選擇的layers
const selection = Document.fromNative(document).selectedLayers;
layers.forEach(item => {
//判斷layer類型是否爲文字
if (item.type === 'Text') {
//更新textlayer
item.text = value;
}
});
})
複製代碼
若是你還不知足於此,說明你真的是個很愛學習,也頗有潛力的開發同窗。一個完善的插件須要包括交互層、API層、業務層、調試層以及發佈層,每層各司其職,它們都在默默幹好本身的工做。
前面的步驟,經過構件菜單欄、建立Webiew完成了交互層的開發;經過Webview的Bridge傳遞用戶操做到插件側代碼,以後調用Sketch API對圖層進行操做,這是API層的工做;而根據自身需求並依託交互層與API層的實現去編寫業務代碼,則是業務層的工做;至此,你應該就擁有了一個可運行的插件了。
但除此以外,在代碼編寫過程當中還須要Lint組件輔助開發,發現問題須要使用各種Dev工具進行調試,經過QA驗證後,須要Cli工具打包併發布插件更新。這一小節,咱們將簡單介紹一些基本的調試層和發佈層知識。
Webpack配置
Skpm默認採用Webpack做爲打包工具。Webpack是一個現代JavaScript應用程序的靜態模塊打包器(Module Bundler)。當Webpack處理應用程序時,它會遞歸地構建一個依賴關係圖(Dependency Graph),其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個Bundle,須要在webpack.config.js進行配置,相似於Android中的Gradle,一樣支持各類插件。
因爲插件的開發者未必是前端同窗,可能以前並無接觸過Webpack,所以咱們在這裏介紹它的一些經常使用配置,讓你有更多的時間關注業務代碼。第一次接觸Webpack是在去年一次公司內部的技術培訓上(美團技術學院提供了不少技術培訓課程,加入咱們就能夠盡情地在知識的海洋中遨遊了),美團MRN項目的打包方案就是Webpack。
在前端圈有各類各樣的打包工具,好比Webpack、Rollup、Gulp、Grunt 等等。RN打包用的是Facebok實現的一套叫作Metro的工具,而美團MRN打包工具的選型是Webpack,由於Webpack具備強大的插件機制和豐富的社區生態,能夠完成複雜的流水線打包工做,Webpack在Plugin開發中一樣發揮了很是重要的做用。Webpack有五個核心概念:
在插件開發中須要處理html、css、sass、jpg、style等各類文件,只有在Webpack中配置相應的Loader後,這些文件才能被處理。並且咱們極可能遇到某些文件須要使用特定的插件,而其它文件又無需處理的狀況。下面的示例中列舉了添加插件、對文件單獨處理以及參數配置這三個經常使用的基本操做。
module.exports = function (config, entry) {
//經常使用功能1:增長插件
config.module.rules.push({
test: /\.(svg)([\?]?.*)$/,
use: [
{
loader: "file-loader",
options: {
outputPath: url => path.join(WEBPACK_DIRECTORY, url),
publicPath: url => {return url;}}}
]
});}
//經常使用功能2:對文件單獨處理
if (entry.script === "src/script.js") {
config.plugins.push(
new htmlWebpackPlugin({ })
);
}
//經常使用功能3:定製js處理
config.module.rules.push({
test: /\.jsx?$/,
use: [
{ loader: "babel-loader",
options: {
presets: [
"@babel/preset-react",
"@babel/preset-env"
],
plugins: [
//引入antd組件庫
["import",{libraryName: "antd",libraryDirectory: "es",style: "css"}]
]}}]
});
複製代碼
ESLint配置
JavaScript是一門很是靈活的語言,不少錯誤每每運行時才爆出,經過配置前端代碼檢查方案,在編寫代碼過程當中可直接獲得錯誤反饋,也能夠進行代碼風格檢查,不只提高了開發效率,同時對不良代碼編寫習慣也能起到糾正做用。在ESLint中須要配置基礎語法規則、React 規則、JSX規則等,因爲Sketch插件的CocoaScript語法較爲特殊,須要配置全局變量以此忽略AppKit中沒法識別的類。
雖然,咱們曾在部門組會中被屢次「安利」ESLint的強大做用(這裏給你們推薦一篇技術文章:ESLint 在中大型團隊的應用實踐),但若是不是作前端或者RN開發的同窗,可能對於ESLint的複雜配置並不熟悉。能夠直接使用Skpm提供的ESlint Config,裏面配置了包含Sketch和macOS的頭文件的全局變量,而代碼格式化則推薦使用Prettier。
npm install --save-dev eslint-config-sketch
//或者直接使用帶prettier以eslint的skpm template工程
$ skpm create my-plugin --template=skpm/with-prettier
複製代碼
內容服務端化
Sketch推出的庫(Library)功能對於維護設計系統或風格指南,起到很是重要的做用,能夠給團隊帶來高效工做體驗,甚至改變設計團隊工做方式和流程。咱們經過組件庫能夠在整個設計團隊中共享組件(Symbol),Library能夠實現「一處更改,到處生效」,即便是關聯了遠程組件庫歷史的設計稿檢測到更新時,也會收到Sketch通知,確保工做中使用的是最新組件。
庫功能對美團外賣UI一致性起着相當重要的做用,這主要體如今兩方面:首先是實現設計風格沉澱,目前袋鼠UI已經造成了本身的獨特風格,外賣設計團隊根據設計規範,對符合UI一致性外賣業務場景的組件不斷進行抽象及建設,沉澱出愈來愈多的通用業務組件,這些組件須要及時擴充到Library中,供團隊成員使用;另一個做用,則是保持團隊使用的均爲最新組件,因爲各類緣由,組件的設計元素(色彩、字體、圓角等屬性)可能會發生變動,須要及時提醒團隊成員更新組件,保持全部頁面的一致性。
庫組件自動更新,其實就是 「庫列表」 - 「庫 ID」 - 「外部組件原始 ID」 這三者的關聯。Sketch內部是靠UUID進行對象識別的,經過庫組件的庫ID,從庫面板的列表中,按照添加的時間重新到舊依次檢索全部未被禁用的、連接無缺的庫,直到匹配到庫的ID ,而後查找該庫文件內是否有與庫組件SymbolID匹配的組件,若是包含且內容有差別就提醒更新,更新的過程其實是內容替換。
咱們經過如下步驟使用RSS技術共享Library供整個UI設計團隊使用:
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle"> <channel> <title>My Sketch Library</title> <description>My Sketch Library</description> <image> <url></url> </image> <item> <title>My Sketch Library</title> <pubDate>Wed, 23 Jun 2019 11:19:04 +0000</pubDate> <enclosure url="mysketchlibrary.sketch" type="application/octet-stream" sparkle:version="1"/> </item> </channel> </rss> 複製代碼
前面一口氣講述了不少內容,可能你一時沒法消化,這裏對插件的開發流程做個簡要的總結:
這部分主要記錄了積木Sketch Plugin開發過程當中的踩坑經歷,可是這裏,咱們沒有貼大段的代碼,沒有直接告訴你答案,而是把分析問題的過程記錄下來。「授人以魚不如授人以漁」,相信只要你瞭解了這些分析技巧,即便以後遇到更多的問題,也能夠輕(jia)鬆(ban)解決。
首先,咱們要明確一個問題,爲何要使用XCode工程?
雖然官方提供了JS API並承諾持續維護,但這項工做一直處於Doing狀態,並且官方文檔更新緩慢,沒有明確的時間節點。所以,對於某些功能,好比咱們想建一個具備Native Inspector Panel的插件,就不得不使用XCode進行開發。使用Xcode開發對於iOS開發者也更加友好,無需再學習前端界面開發知識。
這裏推薦Invison的開發成員James Tang分享的博客文章《Sketch Plugin Xcode Template》,裏面詳細描述了構建插件XCode工程的步驟,這也成爲不少插件開發者遵循的範本。固然隨着Sketch的不斷升級,某些API已經不受支持,但做者講述的開發流程和思路依然沒有改變,具備很高的學習價值。
JavaScript
//利用 Mocha加載framework
var mocha = Mocha.sharedRuntime();
[mocha loadFrameworkWithName:frameworkName inDirectory:pluginRootPath]
複製代碼
除此以外,Skpm中已經內置了@skpm/xcodeproj-loader,也可在JS中直接加載Framework。
JavaScript
//加載framework
const framework = require('../xcode-project-name/project-name.xcodeproj/project.pbxproj');
const nativeClass = framework.getClass('NativeClassName');
//獲取nib文件
const ui = framework.getNib('NativeNibFile');
//也能夠直接加載xib文件
const NibUI = require('../xcode-project-name/view-name.xib')
var nib = NibUI()
let dialog = NSAlert.alloc().init()
dialog.setAccessoryView(nib.getRoot())
dialog.runModal()
複製代碼
固然你也能夠直接使用Github上一些知名的開源項目,有些會直接提供Framework供你使用,好比更改原生的toolbar:
爲何在講述Sketch Plugin的時候,突然會提到Electron?這裏有一個小故事,某天上班打開大象(美團內部溝通軟件)。
看到一條公衆號推送,是公司成立了Electron技術俱樂部(美團技術團隊內部自發成立了不少技術俱樂部),通過了解發現Electron基於Chromium和Node.js,可使用HTML、CSS和JavaScript構建桌面應用程序,Electron負責其中比較複雜的部分,而開發者只需關心應用的核心需求便可。大象的Mac端就大量使用了Electron技術,用Web框架去開發桌面應用,能夠直接複用Web現有的開發成果並得到出色的運行效率。
咱們就進行了簡單的學習,在以後的一段時間並無再去關注這項技術,直到某天在插件開發的過程當中突然遇到一個問題:在插件WebView顯示的狀況下,在桌面空白處點擊使Sketch軟件失去焦點,整個App就會被隱藏。試了幾個流行的插件,發現大部分均有此問題,這給設計師的工做形成了諸多不便。試想,我只是去打開Finder找一個文件,你爲何要把個人軟件最小化?在Github上留言後,很快獲得了項目開發者Mathieu Dutour的官方回覆,原來只須要設置一個hidesOnDeactivate屬性便可。
等等!這不是Electron中的屬性麼?仔細查看Readme才發現做者寫道「The API is mimicking the BrowserWindow API of Electron.」這下可方便多了!你想自定義窗口的表現,只需按照Electron的API設置便可,想一想看其實Electron的工做方式是否是和Sketch Plugin一模一樣?
爲了更好地提高積木Sketch Plugin的使用體驗,UI同窗經過創建公共Wiki記錄咱們設計團隊在插件使用過程當中的反饋建議,其中有一條很奇怪:「經過插件面板更新Layer屬性後,右側面板不刷新。」和上一個問題同樣,經測試其它插件大部分也有此問題,可是如何去更新右側屬性面板呢?翻閱了Sketch的API文檔仍是「丈二和尚,摸不着頭腦」。這個時候想起了macOS開發的一個神器Interface Inspector,它能夠在運行時分析正在運行的Mac應用程序的界面結構和屬性,很是強大。
開心的下載下來後,發現這個軟件上次的更新時間是6年前,突然有了一種不祥的預感。果真Attach任何App時都會提示沒法Attach,在macOS Catalina版本已經沒法運行。但是這怎麼能難倒「萬能」的程序員呢?咱們查看系統報錯,發現是mach_inject_bundle_stub錯誤,查閱發現mach_inject_bundle_stub是Github上的一個開源庫,因此本身下載源碼從新編譯個Bundle包就能夠了。
Attach成功後,就能夠對Sketch的面板進行屬性分析了,是否是突然感受打開了新世界的大門?通過查閱發現右側面板在MSInspectorController中。以下圖所示:
下一步須要用Class-Dump工具來提取Sketch的頭文件,查看能夠對inspector面板進行操做的全部方法:
不出所料,咱們發現了reload(),猜想調用這個方法能夠刷新面板,測試一下發現問題被修復了。若是你使用Sketch的JavaScript API的話,名稱不必定能徹底對應,可是基本差很少,稍加分析也能夠找到。這裏只是教你們一個思路,這樣即便遇到其它問題,按照上面的步驟試試看,沒準就能夠解決。
JavaScript
// reload the inspector to see the changes
var sketch = require('sketch')
var document = sketch.getSelectedDocument()
document.sketchObject.inspectorController().reload()
複製代碼
如你所見,積木Sketch Plugin能夠幫助設計團隊提高設計效率、沉澱設計語言以及減小走查負擔;讓RD同窗面對新項目時,能夠專一於業務需求而無需把時間耗費在組件的編寫上;減小QA工做量,保證控件質量無需頻繁迴歸測試;幫助PM提升版本迭代效率及版本需求吞吐量,提供業務的快速拓展能力。
固然,咱們除了但願製做一流的產品,也但願積木插件可讓你在繁忙的工做中得以喘息。咱們會繼續以設計語言爲依託,以Skecth Plugin爲抓手持續進行UI一致性建設,提升客戶端UI業務中臺能力。
可能對於一個前端工程師來講,對React、Webpack等配置能夠信手拈來;對於一個iOS工程師來講,XCode調試、Objective-C語法是開發前的基礎;對於一個桌面工程師來講,對Electron、Hook分析已司空見慣。可Sketch Plugin開發就是這麼有趣,雖然只是一個小小的插件,但它會讓你接觸各個端的技術,提高技術視野,但一樣會讓你在開發過程當中遇到不少困難,曾經困擾了我好幾天的一個Webpack問題,部門同事幫咱們聯繫了一個開發經驗豐富的前端妹子去諮詢,對方一行代碼居然就解決了。作你懼怕作的事,而後你會發現,不過如此。
目前,積木插件開發還處於較爲初級的階段,包括Mach(外賣自研動態化框架)實時預覽、模板代碼自動生成、自建插畫庫等功能已經在路上。除此以外,咱們還規劃了不少激動人心的功能,須要製做更多精美的前端頁面,須要更完善的後臺管理。
這裏加個廣告吧!無論你是FE、Android、iOS、後端,只要你對Bug絕不手軟,精益求精,都歡迎你加入咱們外賣技術團隊,跟咱們一塊兒完善Sketch插件生態,讓積木插件能夠爲更多業務場景提供服務,爲用戶提供卓越的體驗。讓咱們一塊兒用「積木」拼出萬千世界!
嗯,就先寫到這裏吧!UI團隊同窗說咱們的實現和設計稿居然差了一個像素,咱們要回去改Bug了。
特別感謝優秀的設計師昱翰、沛東、淼林、雪美,他們在插件開發過程當中給予的幫助。
特別感謝技術團隊的雲鵬、曉飛在技術上給予的指導。
「前人栽樹,後人乘涼。」咱們向優秀開源項目開發者致敬。
美團外賣長期招聘 Android、iOS、FE 高級/資深工程師和技術專家,歡迎加入外賣App你們庭。歡迎感興趣的同窗發送簡歷至:tech@meituan.com(郵件標題註明:美團外賣技術團隊)
閱讀更多技術文章,請掃碼關注微信公衆號-美團技術團隊!