积木Sketch Plugin:设计同窗的贴心搭档

| A consistent experience is a better experience.——Mark Eberman | 一致的体验是更好的体验。——Mark Eberman 《摘自设计师的16句名言》css

背景

1.UI一致性项目

积木(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一致性项目

2. Sketch & Sketch Plugin

要想保持UI一致性,就不能打破规则。从设计阶段颜色的选择、字体的规范、控件的样式到RD开发阶段代码的统一管理、API的制定、多端的实现方式,都必须遵照一套规则,而Sketch Plugin建设则是让规范落地执行的解决方案。

在讨论其重要性以前,咱们首先简单介绍一下Sketch:Sketch是一个设计工具包,由总部位于荷兰海牙的BohemianCoding团队开发,该团队成员目前不足百人,来自全球多个国家,经过互联网远程协做开发,属于典型的高效开发团队。

Sketch容易理解且上手简单;可与团队中的每一个人建立、更新和共享全部Symbol组件,实现设计资源的共享和版本管理,今后告别“final-final-final-1”;其版本迭代速度很是快,且能不断添加新功能,知足用户的需求,更符合互联网时代;Sketch可使用真实数据进行设计。目前,咱们设计团队已经全面使用Sketch进行设计。

设计语言包括Iconfont、色板、文字规范、话术、插画、动画、组件等。其实它并非一个抽象的概念,好比你们提到“美团”就会想起“美团黄”,想到可爱的“袋鼠”,想到那些骑着摩托车、穿着印有“美团外卖”亮黄色衣服的骑手小哥。经过设计语言,咱们能够更好地传达品牌主张和设计理念。UI团队逐步将设计语言沉淀为设计规范,并将其量化内置于积木Sketch Plugin中,使产出的设计稿和RD代码库中的组件一一对应,从而造成一个完整的闭环,进而可加速整个业务的交付流程。

使用Sketch Plugin能够快速设计出标准页面

3. 积木Sketch 插件项目

其实,市面上已存在相似插件,为何咱们还要本身动手开发呢?由于UI设计语言与自身业务关联性很强,不一样业务的色彩系统、图形、栅格系统、投影系统、图文关系千差万别,其中任意一环的缺失都会致使一致性被破坏。现有插件所提供的通用设计元素没法知足外卖设计团队的需求,开发一款能够与业务强关联且功能可定制的插件,显得尤其重要。

此外,统一的品牌符号、品牌特征,也有助于加深产品在用户心中的印象,统一的颜色和交互形式能帮助用户加深对产品的熟悉感和信任感,一个好的设计语言自己能够在体验上为产品加分,也可以更好创造一致性的体验。

积木Sketch插件通过一段时间的建设,目前已具有Iconfont、标准色板、组件库、数据填充、文字模板等功能。

咱们经过Iconfont能够从美团的图标库中拉取设计团队上传的SVG图标,并直接应用于设计稿;标准色板能够限定设计师的颜色使用范围,确保设计稿中的颜色均符合设计规范;组件库中包含从外卖业务中抽离的基本控件与通用组件,具备可复用和标准化的特色,并与不一样语言平台组件库中的代码一一对应,使用组件库中的组件进行设计,能够提高UI的设计效率、开发效率以及走查效率;数据填充库能够实现图片填充和文本填充,图片包含了商品及商家素材,文字则包含了菜品、商铺名等信息,经过数据填充可使设计师采用真实数据进行填充,让设计稿更为直观,也更贴近线上环境;文字模板中内置了Head、SubTitle、Body、Caption的使用规范,根据设计稿中文字的位置,点击文字图层便可直接应用字体、行高、字距等属性。

此外,咱们还根据设计同窗的使用反馈,不断增添新功能。同时也在拓展插件的使用场景,增长业务线切换功能,使积木插件能够为更多的团队服务,并期待它能成为更多设计师的“贴心搭档”。

积木Sketch Plugin已支持功能

4. 为何要写这篇文章?

相信你读完上面的内容,确定火烧眉毛的想了解一下Sketch插件,以此迅速提高本身团队开发效率了吧?

其实在开始以前,咱们可先了解一些不利的条件。第一点,因为Sketch更新速度极快,可是官方文档却十分简单且陈旧,所以不少知名的Sketch Plugin因每次API的变动过大纷纷放弃维护;第二点,因为开发技术栈混乱,成熟项目通常还未开源,而开源的项目基本上没有什么参考价值,绝大多数都是“update 3 years ago”;最后一点,macOS开发资料更是少的可怜。

咱们阅读了大量的文档却没有理清头绪,仿佛不少Wiki讲到关键地方,好比某个很是期待的功能是怎么实现的时候,做者居然一笔带过,让人摸不到头脑。知乎上一篇Sketch Plugin的科普文,不少网友会评论“求教学视频,我能够花钱买的”。通过一步步踩坑,咱们就总结了一些开发经验,为了不你们“重复踩坑”,晚上能够早点下班陪陪家人,咱们决定写一篇文章记录下开发的过程。虽然比起那些已经更新多版的成熟项目,但还有很多的差距,至少可让你们再也不那么迷茫。

固然,即便你以为本身是个“跟Sketch八竿子打不着”的开发同窗,咱们也以为这篇文章一样也值得阅读,由于你会经过本文接触到前端、移动端、桌面端、服务端的各类开发知识。咱们都知道,愈来愈多的公司开始喜欢招全栈工程师,像Facebook基本上只招全栈工程师。你内心是否是在想:“是否是在搞笑啊?不过一个插件而已?”先别轻易下结论。

准备好了吗?盘它!

准备放手Coding以前

好,先别着急敲击键盘。毕竟咱们连使用哪一种语言去开发都没决定,这曾经也是困恼咱们许久的一个问题。目前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的命名和使用方式可能会发生较大变化,不少知名插件都所以放弃更新。

本文采用了“混合开发模式”进行讲解,但愿可以给你一些小启发。

Sketch 开发原理

1. Sketch Plugin开发流派

2. 环境配置

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
复制代码

3. 项目结构

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中,好比consolefetchprocessquerystringstreamutil等。

这里你只须要知道如下几点:

  • 须要参与Webpack打包的脚本文件必须在resources目录下声明,不然不会参与编译(重点!考试要考!)。
  • assets目录须要配置在skpm.assets下。
  • 经常使用的命令能够定义在scripts中方便直接调用。
  • dependencies字段指定了项目运行所依赖的模块,devDependencies指定项目开发所须要的模块。
{
  "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": {}
}
复制代码

4. API Reference

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的基本语法以下:

  • Objective-C的方括号语法“[ ]”转换为JavaScript中的点“ . ”语法。
  • Objective-C的属性导出到JavaScript时Getter为object.name() 而Setter为object.name = 'Sketch'。
  • Objective-C的selectors被暴露为JavaScript 的代理方法。
  • “:” 冒号被转换为下划线“ _”, 最后一个下划线是可选的。
  • 调用带有一个下划线的方法须要加倍为两个下划线: sketch_method变为sketch__method。
  • selector的每一个component被链接成不带有分隔符的单个字符串。

5. Actions

行为定义

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')
}
复制代码

6. Bridge双向通讯

在常规的插件开发中,UI层通常采用Webview实现,所以你可使用各类前端开发框架,好比React或者Vue等;而插件的逻辑层(负责调用Skecth API)显然不在WebView中,所以须要经过Bridge进行通讯。逻辑层将从服务器获取到的数据传递给UI层展现,而UI层则将用户的操做反馈传递给逻辑层,使其调用Sketch API更新Layers。

Sketch 通讯原理

插件发送消息到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能够从逻辑层获取一个随机数。

skpm/with-webview 运行效果

快来正式加入开发队伍

相信阅读完上面的部分,制做一个简单的插件对于你来讲,已经有点“游刃有余”了。但这个时候,疑惑也随之而来,为何Demo和咱们经常使用插件的UI差异如此之大?

没错,官方文档只教给咱们最基础的插件开发流程,一个成熟的商业项目毫不仅仅是以上这些。一个功能完善的插件应该包括如下三部分:工具栏、WebView容器以及业务数据。下面,咱们会一步步为你展现如何开发一个商业化插件UI,同时也会演示美团外卖“填充功能”的实现(注:篇幅缘由文档中仅保留关键代码。)

常规Sketch插件结构

1. 建立吸附工具栏

所谓吸附式工具栏,就是展现在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()
复制代码

2. 建立WebView容器

除了经过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'))
复制代码

3. 建立内容页面

历尽千辛万苦,咱们终于拿到了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;
          }
   }); 
})
复制代码

4. 还想加点出彩的功能

若是你还不知足于此,说明你真的是个很爱学习,也颇有潜力的开发同窗。一个完善的插件须要包括交互层、API层、业务层、调试层以及发布层,每层各司其职,它们都在默默干好本身的工做。

前面的步骤,经过构件菜单栏、建立Webiew完成了交互层的开发;经过Webview的Bridge传递用户操做到插件侧代码,以后调用Sketch API对图层进行操做,这是API层的工做;而根据自身需求并依托交互层与API层的实现去编写业务代码,则是业务层的工做;至此,你应该就拥有了一个可运行的插件了。

但除此以外,在代码编写过程当中还须要Lint组件辅助开发,发现问题须要使用各种Dev工具进行调试,经过QA验证后,须要Cli工具打包并发布插件更新。这一小节,咱们将简单介绍一些基本的调试层和发布层知识。

积木Sketch Plugin结构

Webpack配置

Skpm默认采用Webpack做为打包工具。Webpack是一个现代JavaScript应用程序的静态模块打包器(Module Bundler)。当Webpack处理应用程序时,它会递归地构建一个依赖关系图(Dependency Graph),其中包含应用程序须要的每一个模块,而后将全部这些模块打包成一个或多个Bundle,须要在webpack.config.js进行配置,相似于Android中的Gradle,一样支持各类插件。

Webpack处理流程示意

因为插件的开发者未必是前端同窗,可能以前并无接触过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中,供团队成员使用;另一个做用,则是保持团队使用的均为最新组件,因为各类缘由,组件的设计元素(色彩、字体、圆角等属性)可能会发生变动,须要及时提醒团队成员更新组件,保持全部页面的一致性。

Sketch内置的iOS远程组件库

Library中的Symbol提示更新

库组件自动更新,其实就是 “库列表” - “库 ID” - “外部组件原始 ID” 这三者的关联。Sketch内部是靠UUID进行对象识别的,经过库组件的库ID,从库面板的列表中,按照添加的时间重新到旧依次检索全部未被禁用的、连接无缺的库,直到匹配到库的ID ,而后查找该库文件内是否有与库组件SymbolID匹配的组件,若是包含且内容有差别就提醒更新,更新的过程其实是内容替换。

咱们经过如下步骤使用RSS技术共享Library供整个UI设计团队使用:

  • 将Library Document 托管到公司内网服务器上。
  • 建立一个XML文件记录版本信息和更新地址。
  • 最后使用Meyerweb URL编码器之类的工具(或直接encodeURIComponent)对XML feed URL进行编码并将其添加到如下内容:sketch://add-library?url=https://***.xml。
  • 将此URI在浏览器中打开便可。
<?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> 复制代码

5. 开发流程小结

前面一口气讲述了不少内容,可能你一时没法消化,这里对插件的开发流程做个简要的总结:

  • 首先利用JavaScript 或CocoaScript开发操做面板。
  • 使用NPM安装所需依赖。
  • 经过Bridge传递用户操做到插件逻辑侧,经过调用Skecth API对文档进行处理。
  • 使用Webpack进行打包。
  • 经过测试后发布插件更新。

Sketch Plugin开发流程

别人可能没告诉你的事儿

这部分主要记录了积木Sketch Plugin开发过程当中的踩坑经历,可是这里,咱们没有贴大段的代码,没有直接告诉你答案,而是把分析问题的过程记录下来。“授人以鱼不如授人以渔”,相信只要你了解了这些分析技巧,即便以后遇到更多的问题,也能够轻(jia)松(ban)解决。

1. 与Xcode工程混合编译

首先,咱们要明确一个问题,为何要使用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:

2. 了解Electron

为何在讲述Sketch Plugin的时候,突然会提到Electron?这里有一个小故事,某天上班打开大象(美团内部沟通软件)。

MacOS版大象截图

看到一条公众号推送,是公司成立了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一模一样?

3. 更新原生属性面板

为了更好地提高积木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中。以下图所示:

Interface Inspector对Sketch进行运行时分析

下一步须要用Class-Dump工具来提取Sketch的头文件,查看能够对inspector面板进行操做的全部方法:

经过class-dump获得的头文件

不出所料,咱们发现了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(邮件标题注明:美团外卖技术团队)

阅读更多技术文章,请扫码关注微信公众号-美团技术团队!

相关文章
相关标签/搜索