Figma 插件开发 - Vite 环境搭建最近工作主要和 Figma 插件打交道,梳理一些踩坑的经验~ 开始 官方的起
最近工作主要和 Figma 插件打交道,梳理一些踩坑的经验~
开始
官方的起始例子:www.figma.com/plugin-docs…
按步骤将插件文件保存到本地即可,调试时可以右键唤起插件,可以关注下几个功能入口:
- Import plugin from manifest 导入本地插件
- Open console 控制台调试
- Run last plugin 加载最新的插件
目前 figma 插件开发流程没有有效的 hot reload 机制,【加载最新插件】在开发时比较常用,快捷键可以记一下。
插件架构
Figma 的插件架构比较简单,主要关注三部分:
- manifest.json 插件清单
- ui.html 入口
- core.js 入口
manifest.json
{
"name": "test",
"id": "1095700741264679376",
"api": "1.0.0",
"main": "code.js",
"editorType": [
"figma"
],
"ui": "ui.html"
}
ui.html
<h2>Rectangle Creator</h2>
<p>Count: <input id="count" value="5" /></p>
<button id="create">Create</button>
<button id="cancel">Cancel</button>
<script>
document.getElementById('create').onclick = () => {
const textbox = document.getElementById('count');
const count = parseInt(textbox.value, 10);
parent.postMessage({ pluginMessage: { type: 'create-rectangles', count } }, '*');
};
document.getElementById('cancel').onclick = () => {
parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*');
};
</script>
core.js
figma.showUI(__html__);
figma.ui.onmessage = msg => {
if (msg.type === 'create-rectangles') {
const nodes: SceneNode[] = [];
for (let i = 0; i < msg.count; i++) {
const rect = figma.createRectangle();
rect.x = i * 150;
rect.fills = [{type: 'SOLID', color: {r: 1, g: 0.5, b: 0}}];
figma.currentPage.appendChild(rect);
nodes.push(rect);
}
figma.currentPage.selection = nodes;
figma.viewport.scrollAndZoomIntoView(nodes);
}
figma.closePlugin();
};
manifest
清单描述文件主要描述应用入口、支持编辑器类型还有一些权限相关的配置:
- main 用于指定 figma 主应用进程的脚本文件,有点类似 chrome 插件中 background 进程
- ui 用于直指定插件交互面板 html 入口,类似 chrome 插件的 popup 页面
- editorType 用于指定插件支持的编辑器类型,编辑的类型有两种,具体的看区别这里
core
core.js 运行在 figma 应用所提供的沙箱环境,其只能使用 figma 所提供 API 与一些基本的 Javascript 特性包括标准类型、JSON 和 Promise API、与 Uint8Array 一类的二进制对象。,DOM 一类的接口是无法使用的,在 core 中尽量使用 figma 提供的 api 来实现目标操作,一些依赖 DOM API 的操作可以考虑交由 ui 页面来处理。
ui
UI 主要用于插件与用户的交互,本质是一个 figma 主页面中内嵌的 iframe 页面,其使用 postmessage 与 core 进行交互。
附:之前为了方便 core 与 ui 通信还写了个页面通讯工具库 rpc-shooter 有兴趣的同学可以瞧瞧。
想具体了解插件的运行机制可以戳 How Plugins Run。
环境搭建
Vite 这段时间使用下来体验十分友好,下面以 Vite 为例搭建一个 Figma 插件开发环境。
示例的仓库地址:github.com/kinglisky/f…
yarn create vite
这里选用的 Vue 与 TS,框架随意,Figma 插件开发建议使用 TS,接入 figma plugin 的 d.ts 可以方便的当文档使用。
figma plugin 的类型支持需要配置下 tsconfig.json
:
yarn add @figma/plugin-typings -D
{
"compilerOptions": {
...other,
"typeRoots": ["./node_modules/@types", "./node_modules/@figma"]
}
}
多入口
Figma 插件中我们只关心两个入口文件:
- ui.html
- core.js
两个入口文件相互独立,需要将 ui 与 core 拆分成两个包来维护,整理下目录结构拆分出 ui 与 core 部分,示例中还拆分了一个 common 包用于提供公共配置:
.
├── lerna.json
├── manifest.json
├── package.json
├── packages
│ ├── common
│ │ ├── constants
│ │ │ └── ui.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── core
│ │ ├── favicon.svg
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ └── ui
│ ├── index.html
│ ├── package.json
│ ├── src
│ │ ├── App.vue
│ │ ├── env.d.ts
│ │ ├── main.ts
│ │ └── styles
│ │ └── index.css
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── scripts
│ └── build.js
├── tsconfig.json
└── yarn.lock
workspaces
本地进行多包开发时,workspaces 是个很好用的功能,可以将本地包作为依赖来使用,有点类似 webpack 的中 alias 配置将依赖指向本地。
将 packages 目录下的所有包做作为本地依赖:
{
"private": true,
"workspaces": [
"packages/*"
],
...
}
插件中实际 core 与 ui 都会从 common 中读配置,common 包的 package.json 配置下 name 与 module 即可提供给 core 与 ui 使用:
{
"name": "figma-vite-common",
"private": false,
"version": "0.0.0",
"module": "./",
"types": "./"
}
core 与 ui 使用时:
import { VIEW_WIDTH, VIEW_HEIGHT } from 'figma-vite-common/constants/ui';
构建配置
有一点需要注意一下,core.js 与 ui.html 作为插件的入口文件只能是个单文件,简单来说就是不能拆分文件,不能使用异步模块导出多个文件。
- core 中所有依赖的资源都打包到 core.js
- ui.html 中所有的资源都会以内联形式存在
core 配置
core 的配置比较简单:
import { resolve } from 'path';
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
formats: ['iife'],
name: 'core',
fileName: 'core',
},
outDir: resolve(__dirname, '../../dist'),
},
});
由于不需要发包使用,将最终打包的 core 入口格式配置为 iife
(立即执行的函数表达式模块),简单的包一层立即调用函数即可:
// 构建结果
(function () {
'use strict';
figma.showUI(__html__, { width: 400, height: 490 }),
(figma.ui.onmessage = (o) => {
if (o.type === 'create-rectangles') {
const t = [];
for (let i = 0; i < o.count; i++) {
const e = figma.createRectangle();
(e.x = i * 150),
(e.fills = [{ type: 'SOLID', color: { r: 1, g: 0.5, b: 0 } }]),
figma.currentPage.appendChild(e),
t.push(e);
}
(figma.currentPage.selection = t),
figma.viewport.scrollAndZoomIntoView(t);
}
figma.closePlugin();
});
})();
对应的配置下 package scripts:
{
"name": "figma-vite-core",
"private": false,
"version": "0.0.0",
"scripts": {
"start": "tsc && vite build -w",
"build": "tsc && vite build"
},
"dependencies": {
"figma-vite-common": "*"
},
"devDependencies": {
"@figma/plugin-typings": "^1.42.1",
"typescript": "^4.5.4",
"vite": "^2.8.0"
}
}
开发时会以 yarn start 启动, -w 监听文件变动触发构建。
ui 配置
ui 由于最终的构建产物是一个 html 文件,其他的构建的资源需要内联,这里需要将各种代码拆分的配置关闭,内联资源的阈值调大:
import { resolve } from 'path';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { viteSingleFile } from 'vite-plugin-singlefile';
export default defineConfig({
plugins: [vue(), viteSingleFile()],
build: {
target: ['es6'],
assetsInlineLimit: 100000000,
chunkSizeWarningLimit: 100000000,
cssCodeSplit: false,
brotliSize: false,
outDir: resolve(__dirname, '../../dist'),
rollupOptions: {
inlineDynamicImports: true,
output: {
format: 'iife',
manualChunks: () => 'everything.js',
},
},
},
});
vite-plugin-singlefile 插件用于将构建出的 js 资源内联到 html 中,构建的包格式还是 iife
。
package scripts 配置:
{
"name": "figma-vite-ui",
"private": false,
"version": "0.0.0",
"scripts": {
"start": "vue-tsc --noEmit && vite build -w",
"build": "vue-tsc --noEmit && vite build"
},
"dependencies": {
"figma-vite-common": "*",
"vue": "^3.2.25"
},
"devDependencies": {
"@figma/plugin-typings": "^1.42.1",
"@vitejs/plugin-vue": "^2.2.0",
"sass": "^1.49.7",
"typescript": "^4.5.4",
"vite": "^2.8.0",
"vite-plugin-singlefile": "^0.6.3",
"vue-tsc": "^0.29.8"
}
}
同样使用 yarn start 启动,-w 监听变动构建。
启动入口
实际在插件开发时需要同时启动 core 与 ui,所以可以在根目录下配置个启动命令,可以直接使用 lerna 进行启动。
配置 lerna.json
{
"packages": ["packages/*"],
"version": "0.0.0",
"npmClient": "yarn",
"useWorkspaces": true
}
配置 package.json
{
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"start": "lerna run --stream --scope figma-vite-ui --scope figma-vite-core start"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"lerna": "^4.0.0"
},
"name": "fgima-vite"
}
最后改一下插件manifest.json
中入口文件的路径:
{
"name": "figma-vite-demo",
"id": "",
"api": "1.0.0",
"main": "dist/core.iife.js",
"ui": "dist/index.html",
"editorType": ["figma"]
}
自此一个简单 Figma 插件开发环境完成,仓库在这 github.com/kinglisky/f…
其他
有空会梳理下 Figma 文档数据结构与一些解析技巧,先这样~
转载自:https://juejin.cn/post/7090755655027392525