Figma 插件开发入门指南在实践现代化前端开发之前,我工作的大部分时间是做 Android 开发。本文是我在开发 Fi
在很久很久以前,我开发过一个 Figma 插件,它的样子是这样的:
功能是齐全的,但就是太丑了,一直想着优化优化。但一年多过去了,没有任何进展(被前端的 UI 框架吓跑了)。最近,工作上正好有 Figma 插件相关的开发任务,”浅浅”的入门了一下前端,顺便把这个 Figma 插件优化了,优化之后是这样的:
该 Figma 插件链接:www.figma.com/community/p…
是不是感觉高大上了许多!如果你也有 Figma 插件开发的想法,可以考虑继续看下去,这篇文章或许可以帮助到你。
本文会先介绍一些必备的前端基础知识,可以当作 checklist 去学习和了解,再介绍 Figma 插件相关的知识,最后再用上边的 Figma 插件源码作为 demo 进行讲解。
值得说明的是,本文不会包含 Figma 插件新建,导出等等非常基础的知识点,如果有需要的话,可以查看其他的博客。
前端基础知识学习
本节主要介绍 Figma 插件开发的学习路径,并给每项任务的难度给出主观评分(难度最高五颗星:⭐⭐⭐⭐⭐),由于每个人的知识储备不一样,难度评分仅供参考。
-
学习 Typescript 语言,难度:⭐⭐
在此之前,自己熟悉的语言主要有三个:Kotlin、Python、Java。而 TypeScript 可以理解成带了 Javascript 语法糖的面向对象语言,所以个人感觉学起来没什么难度。简单看看即可,使用时遇到不会的再复习一下。
学习时间:8h ~ 16h
-
JavaScript 的 async/await 语法,难度:⭐
个人比较喜欢用协程,所以在这里单独推荐学习一下。使用时值得注意的点:需要圈定好 async 的作用域,不然会出现代码没有按预期顺序执行的情况。
学习方式:搜索引擎
学习时间:1h
-
npm 的使用,难度:⭐⭐
前端使用 npm 来实现多模块的管理,学会 npm 的使用非常重要。大致需要学习:npm 基础命令、npm 多模块管理的配置、package.json 文件中各个字段的定义和作用。
学习方式:ChatGPT
学习时间:2~3h
-
React 的学习和使用,难度:⭐⭐⭐
由于自己之前使用过 Jetpack Compose,其和 React 的使用有很大的相似度,所以选择了 React 来进行前端开发。学习 React 时,需要学习 tsx 的语法,以及 React Hooks 函数的使用(
useState
、useEffect
、useCallback
、useContext
),外加一些简单的 css style 的学习,这些对于入门级的 UI 开发已经足够。学习方式:React 官网 && ChatGPT
学习时间:3h
-
Ant Design 的了解和使用,难度:⭐
Ant Design 是基于 React 的 UI 组件库,可以极大的提高 UI 开发效率。可以先简单了解一下其支持的组件,具体使用时再复制粘贴对应组件源代码即可。
使用方式:设计 UI 稿时,复制粘贴 该 Figma 组件库 的相关组件至自己的 Figma 设计稿,书写代码时复制粘贴对应组件代码即可。
浏览时间:1h
-
其他前端框架的了解和使用,难度:⭐⭐
不得不说,现代化的前端开发,使用到的开发框架还不少。可以先简单了解即可,之后参考一下 TextColor2Android 是如何使用的,基本就算学会了。
需要了解的框架有:Vite (前端项目编译打包的框架)、turbo (编译管理大型项目)、next.js (方便 React UI 调试的开发框架)。
了解时间:2h
整体来看,前端知识的学习,难度平均 1.8 颗⭐,总耗时 17 ~ 26h,可以根据自己的经验和实际情况安排具体的学习计划。
Figma 插件开发基础知识
本节会罗列在开发 Figma 插件时所涉及到的知识点,覆盖率大概在 95% 左右。当然,一些类似于 SceneNode 属性相关的知识就不会过多介绍了,这些细节的知识得自行查阅 Figma Plugin Docs 获得。
同时,为了更好的理解,强烈建议配合 TextColor2Android 源代码一起食用。
-
简单理解 Figma 插件的运行原理
Figma 运行时,分为 Figma 沙盒环境(main)和插件 HTML(ui) 环境。只有在 Figma 沙盒环境(main)中才能访问 Figma 的 API,读写 Figma 的设计稿,两者通过 postMessage 和 onMessage 通信。运行原理详情可查看 How Plugins Run
由此可以引申到 Figma 插件 manifest.json 配置中 main 和 ui 文件的路径定义:
{ "main": "path/to/your/code.js", "ui": "path/to/your/index.html" }
main(code.js) 文件的逻辑在 Figma 沙盒环境中运行,ui(index.html) 在非沙盒环境中运行。html 的展示通过在 code.js 中调用如下代码实现:
figma.showUI(__html__, { width: 450, height: 550, themeColors: true });
-
插件代码的编译
Figma 插件的 UI 代码需要编译成单个 html 文件,我们可以使用 vite 的 vite-plugin-singlefile 来实现。
而 main 代码可以使用 esbuild,将 Typescript 代码编译成 JavaScript 代码。
相关逻辑可以查看 TextColor2Android 的 plugin/package.json/scripts,获取具体的编译脚本细节
-
main 和 ui 进行通信
发送的消息,默认支持 Javascript 的基本数据类型,非基本数据类型需要自行实现序列化和反序列化逻辑。
ui 发送消息给 main:
// ⚠️ 值得注意: // 1. 一定得是 pluginMessage 字段,不然 main 会接收不到 // 2. messageContent 为发送消息的具体内容 parent.postMessage({ pluginMessage: messageContent }, "*")
ui 接收 main 发送的消息:
使用
window.addEventListener('message', messageHandler)
的方式实现,这里提供一个自定义的 Hook 供参考:// 如果只接收一次消息,则 oneShot 传入 true export const useEffectRegisterOnMessage = (callback: (message: any) => void, oneShot: boolean = false) => { const [flag, setFlag] = useState(true) useEffect(() => { const messageHandler = (event: MessageEvent) => { if (oneShot && !flag) return setFlag(false); const message = event.data.pluginMessage; if (message) { console.log("Recieve msg from Figma backend: ", message); callback(message); } } window.addEventListener('message', messageHandler); return () => { window.removeEventListener('message', messageHandler); } }, [callback]); };
main 发送消息给 ui:
figma.ui.postMessage(messageContent)
main 接收 ui 发送的消息:
figma.ui.onmessage = (msg) => { console.log("Recieve msg from UI: ", msg); };
-
使用 on API 监听 Figma 内容的变化 ref
// 支持 selection 和 current page 等 figma.on("selectionchange", () => { console.log("selectionchange: ", figma.currentPage.selection) })
-
实现数据的持久化
调用
figma.clientStorage
API 即可,基于键值对存储,使用很方便。每个插件最多 1M 的持久化空间
-
对 Figma 设计稿添加版本保存的 tag
在执行一些操作之后,你可能需要在 Figma 的 History 留下一个版本号
figma.saveVersionHistoryAsync("vtest");
-
解决 UI 卡顿的问题
由于 Javascript 的渲染是单线程模型,在操作 Figma 节点的时候,会抢占当前的 CPU 资源,导致 UI 刷新卡顿的情况。
解决方式也很简单,分配给 UI 刷新一点时间片即可:
async function function1(): Promise<Data> { await delay(155) // render UI // process Figma Node } function delay(time: number = 40) { return new Promise((resolve) => setTimeout(resolve, time)); }
-
添加 Figma 依赖
最后,也是最重要的,就是添加 Figma 依赖了。
在 package.json 中添加:
"dependencies": { "@figma/plugin-typings": "^1.84.0" }
因为 Figma 的 type 和其他的库不太一样,所以得在 tsconfig.json 中添加编译参数(参考 TextColor2Android 即可):
"compilerOptions": { "typeRoots": ["./node_modules/@types", "./node_modules/@figma"] }
TextColor2Android 项目简介
-
文件结构简介
依稀记得自己拿到前端项目时,一脸懵逼的感觉,所以这里简单介绍一下前端项目的目录结构。以 TextColor2Android 为例,目录截图如下:
- pnpm-workspace.yaml:记录了该项目下,由 pnpm 管理的各个模块的路径信息(pnpm 是 npm 的优化版本,可以使用 npm 安装)
- manifest.json:Figma 插件的配置文件
- ./app 和 ./packages:各个模块的父目录
- turbo.json:turbo 插件的配置信息,可以先不关注
- pnpm-lock.yaml:使用 pnpm 进行包管理时,自动生成的文件,可以不用关注
-
各模块简介
- model:存放公共的数据实体,几乎所有模块都会引用到
- tsconfig:存放公共的 tsconfig.json,几乎所有模块都会引用到
- gen-code:读取 Figma 节点内容,生成 Android 目标代码
- view-model:消息分发层,接收 UI 的事件,实现消息的分发,更新 UI 实体,推送当前的 UI 实体给插件进行展示
- plugin-ui:Figma 插件 UI 代码
- debug:使用 next.js 驱动,调试 plugin-ui 模块
- plugin:组装出整个插件运行逻辑,通过引用 plugin-ui 和 view-model 分别构建出 UI 逻辑和 Figma 沙盒逻辑
-
MVI(Model-View-Intent) 的 UI 开发模式简介
如上是 MVI 的示意图,简单来说,MVI 的基本工作流程:View 发送 Intent 给 ViewModel,ViewModel 处理完 Intent 之后,返回 State 给 View 进行渲染展示,UI 拿到的永远是最新最全的 State,所以可以规避很多 UI 渲染上的问题。
在 TextColor2Android 中,MVI 是由 view-model 模块实现的。
很明显,我们会用到策略模式来做消息分发,接收到对应类型的消息,执行对应的逻辑,如下简单介绍一下在 TextColor2Android 中 MVI 的实现细节。
定义消息处理的接口:
// iEventProcessor.ts export interface IEventProcessor { messageType: UIEventMsgType /** * * @param event receive from UI * @returns wheather send message to notify ui or not, * default has no return value -> send msg */ process: (event: PluginUIEvent) => Promise<boolean | void> }
每个不同类型的消息,书写对应的实现,为了简单起见,更新 UI State 也放到了消息处理器里边(如下以 GetColorCode 这个 messageType 为例):
// getColorCodeProcessor.ts export class GetColorCodeProcessor implements IEventProcessor { messageType = UIEventMsgType.GetColorCode; async process(event: PluginUIEvent) { const codeRes = genCode(event.colorCodeType) // 更新 UI State updateCurUIStateData({ codeType: event.colorCodeType, code: codeRes }) } }
将每个消息处理的实体放到 map 集合中进行管理:
// processorFactory.ts private static processors = [ new CopyToClipboardProcessor(), new InitPluginProcessor(), new GetColorCodeProcessor(), // another msg processor ].reduce( (map, processor) => map.set(processor.messageType, processor), new Map<UIEventMsgType, IEventProcessor> )
由统一的 factory 接收消息,操作 map 集合,实现消息的分发,消息处理完成,发送当前的 UI State 给 UI:
// processorFactory.ts static async doProcess(event: PluginUIEvent) { const needNotifyUI = await ProcessorFactory.processors.get(event.msgType)?.process(event); if (needNotifyUI === undefined || needNotifyUI === true) { figma.ui.postMessage(curUIStateData) } }
-
基于 TextColor2Android 进行二次开发
如果你也有开发 Figma 插件的需要,可以考虑参照或者使用 TextColor2Android 为模板进行开发,具体操作为:
- 新增/修改 view-model 模块的消息类型
- 新增 model 模块的 UI State
- 修改 plugin-ui 模块的 UI 逻辑
- 新增其他相关的模块
- 复用大部分 debug、plugin、tsconfig 模块的逻辑
最后
在实践现代化前端开发之前,我工作的大部分时间是做 Android 开发。本文是我在开发 Figma 插件时,所总结的一些学习路径及记录的经验总结。相信对想进行 Figma 插件开发,但是又无从下手的你,会有所帮助~ 同时,如果你对 TextColor2Android 插件感兴趣的话,可以点击 Reference 下的链接跳转到 Figma 插件主页查看详细信息。
Reference
转载自:https://juejin.cn/post/7385776238491779109