即时设计插件开发工具套件(插件+React+Vite)

494 阅读3分钟

简介

近期在将 XD 等设计资源迁移到即时设计时,有些功能在即时设计上需要使用插件来实现。

在开发插件时,即时设计官方的宿主与插件UI页面,使用 postMessage 通信(经过了官方的一定的封装),使用方法与原生 js 中的 postMessage 基本一致。

由于 postMessage 特性,双方通信需通过类似发布与监听的方式进行开发,虽然通信简单,但有时候不太方便。

本文提供一个用于开发时的辅助套件,方便宿主与 iframe 通信。

同时本项目,也可做为使用 React 进行即时设计插件开发的基础模板。

官方方案

UI 向宿主发送消息

parent.postMessage({ 
    pluginMessage: {type: 'xxx', msg: 'hi host, from ui'} 
}, '*')

宿主接收 UI 的消息

jsDesign.ui.onmessage = (message) => {
  console.log("收到来自前端的消息", message)
  switch(message.type) {
      ...区分不同消息,进行不同处理...
  }
}

宿主向 UI 发送消息

jsDesign.ui.postMessage({type: 'xxx', msg: 'hi ui, from host'} )

UI 接收宿主的消息

window.onmessage = (event) => {
  console.log("收到来自主线程脚本的消息", event.data.pluginMessage)
  if(message.type === 'changeColor') {
      ...区分不同消息,进行不同处理...
  }
}

可以看出使用方法与原生 postMessage 基本一致。

这里有两个问题:

  1. 通过重置 window.onmessage 和 jsDesign.ui.onmessage 来达到消息监听的目的。导致所有消息通过一个方法接收,开发者需要通过 switch/if 等对消息进行区分。(官方也有on/off等类似于addEventListener的方法,经测试,消息会被重复收到多次,原因不明,未敢实际用于开发)

  2. 不能区分每次通信。 比如多次调用一个add方法:

postMessage({type: 'add', args: [1, 2]})
postMessage({type: 'add', args: [3, 4]})

宿主收到消息后,进行相加然后返回结果,但是结果并不是同步返回,而是再次通过postMessage。即:

jsDesign.ui.postMessage({type: 'add-response', result: 3} )
jsDesign.ui.postMessage({type: 'add-response', result: 7} )

除非手动在每个消息中传递一个唯一id用于区分,否则 ui 收到结果后并不知道31+2的结果还是3+4的结果

解决方案

使用此封装好的通信工具,在宿主与 UI 间进行"同步通信"。

ui 调用 host 中的方法

首先在 host 中提供一个 createRectangle 方法:

import {createUiBridge} from '@jsdkit/bridge';

const ui = createUiBridge();

// 定义一个方法,供 ui 环境使用
ui.createRectangle = async (count) => {
    console.log('ui 传递的参数:', count);  // 123
    // 使用 return 将值直接传递给 ui
    return true;
};

jsDesign.showUI(__html__);

在 ui 环境中,直接调用宿主中的 createRectangle 方法:

import {createHostBridge} from '@jsdkit/bridge';

const host = createHostBridge('你的插件id');
const res = await host.createRectangle(123);
console.log(res)  // res 即为 host 中 createRectangle 方法的返回值 (true)

host 调用 ui 中的方法

首先在 ui 中提供一个 changeButtonColor 方法:

import {createHostBridge} from '@jsdkit/bridge';

const host = createHostBridge('你的插件id');

// 定义一个方法供 host 调用
host.changeButtonColor = (color: string) => {
    // 收到 color 参数
    document.getElementById('btn').style.backgroundColor = color;
};

在 host 环境中,直接调用 ui 中的 changeButtonColor 方法:

// 调用 ui 方法
import {createUiBridge} from '@jsdkit/bridge';

const ui = createUiBridge();
ui.changeButtonColor('#f00'); // 如果有返回值,也可以使用 await 来接收
jsDesign.showUI(__html__);

TypeScript 支持

您只需要对提供的方法进行声明,并在 createBridge 时传入泛型声明即可:

示例:

// interfaces.ts ,此文件仅为 ts 定义文件,会被 host 和 ui 环境共用类型提示
export interface Bridge {
    /** 创建矩形 */
    createRectangle(count: number): Promise<boolean>;

    /** 改变按钮颜色 */
    changeButtonColor(color: string): void;
}

在 ui 中使用:

const host = createHostBridge<Bridge>('你的插件id');

之后输入 host.,您的编辑器即可自动提示。

在 host 中使用:

const ui = createUiBridge<Bridge>();

之后输入 ui.,您的编辑器即可自动提示。