BB

上回简单介绍了下 Figma 插件的创建基本流程,这次我们将深入了解插件原理并使用 vue3 + vite 快速创建一个插件项目。

Figma 插件开发

Figma 插件原理

首先 Figma 的插件在平台里怎么运行的,看这里 👉 How Plugins Run
感兴趣的朋友也可以查看 Figma 博客 How to build a plugin system on the web ,了解一下整个插件系统设计的心路历程。

插件组成

Figma 插件分为两个部分:

  • code.js:运行在主进程隔离沙箱里,可访问专门的 API 来操作 Figma 文档
  • ui.html:通过 iframe 嵌入在页面里的插件UI

两部分通过 postMessage 进行通信。

使用 Vue 3 + Vite 开发 Figma 插件 - 图1

截屏2022-04-24 下午11.24.32.png整个插件配置文件 manifest.json 也很简单明了。

  1. {
  2. // 插件名称
  3. "name": "quanquan",
  4. // 插件ID 自动生成
  5. "id": "109824696931xxxx519",
  6. // api 版本号
  7. "api": "1.0.0",
  8. // 运行在主进程隔离沙箱的 api 操作代码
  9. "main": "code.js",
  10. // 插件类型
  11. "editorType": [
  12. "figma"
  13. ],
  14. // 插件 UI
  15. "ui": "ui.html"
  16. }

but 对于复杂的插件来说,用原生 js、css 来开发效率非常低效,所以趁着 vue3 新文档的发布,咱决定使用 vue 3 + vite搭建一个快速开发插件的模板。

go!

创建项目

  1. yarn create vite vue3-figma-plugin-starter --template vue-ts
  2. cd vue3-figma-plugin-starter
  3. yarn
  4. yarn dev

截屏2022-04-25 上午12.06.55.png
然后动动小鼠标打开http://localhost:3000 咱们就已经成功了一半 使用 Vue 3 + Vite 开发 Figma 插件 - 图4

迁移代码

image.png

现在呢,我就有了两个项目文件,一是通过 Figma desktop app 创建的插件项目 quanquan,和另一个用 vite 脚手架创建的 vue3-figma-plugin-starter。

首先咱先考虑下迁移策略,🤔 从上面我们知道, Figma 插件其实只需要 3 个文件 code.js 、ui.html 以及配置文件 manifest.json。

而 vite 项目构建后会在 dist 目录下生成一个 index.html 作为入口文件, public 目录下的文件在构建时也会被复制到 dist 目录下。

那么,我们需要做的就是:

  • 将 manifest.json 放到 public 根目录下,并把 ui 地址改为 index.html 即 "ui": "index.html"
  • 在根目录新建 figma 文件夹,将 code.ts 复制进去,并配置上构建脚本,将其自动编译成 code.js 并输出到 dist 目录下
  • 使用 vue 实现原 ui.html 的功能

添加 ts 声明

第一步,安装上必要的声明依赖

  1. # 添加 figma ts声明
  2. yarn add -D @figma/plugin-typings

迁移配置文件

将 manifest.json 放到 public 根目录下

  1. {
  2. "name": "quanquan",
  3. "id": "1098246969318987519",
  4. "api": "1.0.0",
  5. "main": "code.js",
  6. "editorType": [
  7. "figma"
  8. ],
  9. // ui.html -> index.html
  10. "ui": "index.html"
  11. }

添加 code.ts 及构建脚本

在根目录新建 figma 文件夹,将原 code.ts 复制进去
PS. 不要忘记在文件顶部声明依赖

  1. /// <reference types="@figma/plugin-typings" />
  2. // This plugin will open a window to prompt the user to enter a number, and
  3. // it will then create that many rectangles on the screen.
  4. // This file holds the main code for the plugins. It has access to the *document*.
  5. // You can access browser APIs in the <script> tag inside "ui.html" which has a
  6. // full browser environment (see documentation).
  7. // This shows the HTML page in "ui.html".
  8. figma.showUI(__html__);
  9. // Calls to "parent.postMessage" from within the HTML page will trigger this
  10. // callback. The callback will be passed the "pluginMessage" property of the
  11. // posted message.
  12. figma.ui.onmessage = msg => {
  13. // One way of distinguishing between different types of messages sent from
  14. // your HTML page is to use an object with a "type" property like this.
  15. if (msg.type === 'create-rectangles') {
  16. const nodes: SceneNode[] = [];
  17. for (let i = 0; i < msg.count; i++) {
  18. const rect = figma.createRectangle();
  19. rect.x = i * 150;
  20. rect.fills = [{type: 'SOLID', color: {r: 1, g: 0.5, b: 0}}];
  21. figma.currentPage.appendChild(rect);
  22. nodes.push(rect);
  23. }
  24. figma.currentPage.selection = nodes;
  25. figma.viewport.scrollAndZoomIntoView(nodes);
  26. }
  27. // Make sure to close the plugin when you're done. Otherwise the plugin will
  28. // keep running, which shows the cancel button at the bottom of the screen.
  29. figma.closePlugin();
  30. };

配置 rollupOptions,构建 code.ts

  1. import { defineConfig } from 'vite'
  2. import vue from '@vitejs/plugin-vue'
  3. // https://vitejs.dev/config/
  4. export default defineConfig({
  5. plugins: [vue()],
  6. build: {
  7. rollupOptions: {
  8. input: {
  9. index: 'index.html',
  10. code: 'figma/code.ts'
  11. },
  12. output: {
  13. entryFileNames: '[name].js'
  14. }
  15. }
  16. }
  17. })

改写ui.html

最后再用 vue3 重新编写原 ui.html 页面功能

  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. const count = ref(5)
  4. const create = () => {
  5. parent.postMessage({ pluginMessage: { type: 'create-rectangles', count: count.value } }, '*')
  6. }
  7. const cancel = () => {
  8. parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*')
  9. }
  10. </script>
  11. <template>
  12. <h2>Rectangle Creator</h2>
  13. <p>Count: <input v-model="count" /></p>
  14. <button @click="create">Create</button>
  15. <button @click="cancel">Cancel</button>
  16. </template>

over! 迁移结束,yarn dev 运行看看,正常 使用 Vue 3 + Vite 开发 Figma 插件 - 图6
image.png
yarn build 构建一下,没有报错 使用 Vue 3 + Vite 开发 Figma 插件 - 图8
image.png

那么到目前为止,我们的目录结构就是这样的,一家人👪在 dist 目录下整整齐齐
image.png

运行插件

回到 Figma 里运行一下,选择 Plugins -> Development -> Import plugin from manifest… ,导入 dist 目录下的 manifest.json。

见证奇迹的时刻就要到了,朋友们!
当当当当~
image.png

没有任何东西使用 Vue 3 + Vite 开发 Figma 插件 - 图12
为了缓解这尴尬的气氛,我们来放首歌吧
点击查看【music163】

Tell Me Why

听完歌翻了下官网,发现了这么一句话 🚬

But all the code must be in one file —— Libraries and bundling

还给了 ReactWebpack 的示例,巧了嘛,这不是,我用的 Vue 和 vite (Rollup)🚬

你要代码都放在一个文件里,那我把 js、css 直接构建到 index.html 里不就可以嘛

https://stackoverflow.com/questions/69585063/vue-js-package-app-as-figma-plugin-using-vite/69602056#69602056

  1. # 安装 singlefile 插件
  2. yarn add -D vite-plugin-singlefile@0.7.1

:::warning 至于为什么要指定 0.7.1 版本,因为 ERR_REQUIRE_ESM https://github.com/richardtallent/vite-plugin-singlefile/issues/23 :::

vite.config 中配置插件及资源阈值及禁止 css 拆分

  1. import { defineConfig } from 'vite'
  2. import vue from '@vitejs/plugin-vue'
  3. import { viteSingleFile } from 'vite-plugin-singlefile'
  4. // https://vitejs.dev/config/
  5. export default defineConfig({
  6. plugins: [vue(), viteSingleFile()],
  7. build: {
  8. // https://vitejs.cn/config/#build-csscodesplit
  9. cssCodeSplit: false,
  10. // https://vitejs.cn/config/#build-assetsinlinelimit
  11. assetsInlineLimit: 100000000,
  12. rollupOptions: {
  13. input: {
  14. index: 'index.html',
  15. code: 'figma/code.ts'
  16. },
  17. output: {
  18. entryFileNames: '[name].js'
  19. }
  20. }
  21. }
  22. })

重新构建运行下,我的奇迹回来了
image.png

另一种方式

PS.
后面我打开了控制台看了下资源加载,发现前面插件空白的根本原因其实在 Figma 的资源加载方式上

You have to use absolute URLs starting with http:// or https:// and host the resources on your own server. —— Resource Links

普通构建后的 js、css 文件都是相对路径,在 Figma 插件里其实是找不到的,如果将资源地址改为绝对路径,其实也能解决问题,比如在本地指定端口启动静态站点服务

  1. import { defineConfig } from 'vite'
  2. import vue from '@vitejs/plugin-vue'
  3. // https://vitejs.dev/config/
  4. export default defineConfig({
  5. plugins: [vue()],
  6. base: 'http://localhost:8080/',
  7. build: {
  8. rollupOptions: {
  9. input: {
  10. index: 'index.html',
  11. code: 'figma/code.ts'
  12. },
  13. output: {
  14. entryFileNames: '[name].js'
  15. }
  16. }
  17. }
  18. })
  1. {
  2. "name": "vue3-figma-plugin-starter",
  3. "private": true,
  4. "version": "0.0.0",
  5. "scripts": {
  6. "dev": "vite",
  7. "build": "vue-tsc --noEmit && vite build",
  8. "preview": "vite preview --port 8080"
  9. },
  10. ...
  11. }
  1. yarn build
  2. yarn preview

image.png

这种方式在调试的时候可以这么去用,但发布的时候,你就得将资源文件上传到 WEB 服务器( NGINX or Apache )或静态文件云存储,并提供对外的绝对路径。
且不说,麻不麻烦,index.html 又做错了什么要抛弃它 😭

所以呢,一家人最重要的就是整整齐齐,还是用第一种方式打包在一起吧。

插件调试

其实对于调试我现在也没有找到更好的方法,前面一篇文章也说了,不管是 html 还是 code.js 的修改在 Figma 里都需要手动重新加载一次。

所以我现在一般是先开发样式,使用 yarn dev ,在浏览器中打开移动端调试工具,输入插件设置的宽高。
image.png

然后再配置上 build watch, 在 Figma 里调试操作文档相关的功能,也算实现了半自动化

  1. {
  2. "name": "vue3-figma-plugin-starter",
  3. "private": true,
  4. "version": "0.0.0",
  5. "scripts": {
  6. "dev": "vite",
  7. "build": "vue-tsc --noEmit && vite build",
  8. "watch": "vue-tsc --noEmit && vite build --watch",
  9. "preview": "vite preview --port 8080"
  10. },
  11. ...
  12. }

image.png
如果有其他更方便的调试方法,欢迎在评论区留言讨论 使用 Vue 3 + Vite 开发 Figma 插件 - 图17

最后的最后,本文中的 vue3-figma-plugin-starter 项目已开源在 GitHub 中

有需要的小伙伴可自行下载

广告时间

RemixIcon 的 Figma 插件已上线,欢迎使用 👏🏻