写了一个 MasterGo 转 Figma 的插件

358 阅读6分钟

项目地址:XuWeinan123/MasterGo2Figma

这段时间我在折腾一个小工具:把 MasterGo 里的设计稿迁到 Figma。

做法是:在 MasterGo 端用插件读取页面和图层,导出一个 MasterGo2Figma JSON zip;再在 Figma 端用另一个插件上传这个 zip,把页面和图层尽量还原出来。

为什么没有继续走 Sketch 中转

我最早也考虑过中转方案。比如从 MasterGo 导出 Sketch,再想办法导入 Figma。

这个方向看起来省事,但实际会带来几个问题:

  • 链路变长,排查问题很麻烦。
  • 不同工具对图层属性的理解不一样,中间格式会吃掉一部分信息。
  • 一旦出现样式、布局、组件、布尔运算之类的问题,很难判断是哪一步丢的。

后来我把方案收敛到一件事:自己定义一个中间包。

这个包本质上是一个 zip,里面放 manifest、页面数据、图层 JSON、图片资源。MasterGo 端只负责尽量完整地导出,Figma 端只负责按这套结构还原。这样问题会直接很多:导出错了看 JSON,导入错了看还原逻辑。

整体架构

项目里现在主要有:

SendToFigma
  MasterGo 端插件,负责读取页面和图层,导出 zip

ReceiveFromMasterGo
  Figma 端插件,负责上传 zip,还原页面和图层

整体链路是这样:

MasterGo 页面
  -> SendToFigma 读取图层
  -> 生成 MasterGo2Figma JSON package
  -> 导出 zip
  -> ReceiveFromMasterGo 读取 zip
  -> 在 Figma 中创建页面和节点

zip 结构是可检查的,图层数据也是 JSON。中间产物朴素一点,方便调试。

MasterGo 端:把设计稿导出来

SendToFigma 是发送端插件。

它运行在 MasterGo 里,主要做几件事:

  • 让用户选择要导出的页面。
  • 遍历页面里的图层节点。
  • 把图层属性转换成项目自己的 record。
  • 收集图片等资源。
  • 生成 zip,或者把数据分块发给本地中继服务。

小文件可以直接在插件 UI 里生成 zip。这个方式最简单,点一下就能下载。

但页面一大,问题就来了。插件 UI 里拼大 zip、创建 Blob、触发下载,都会吃内存。图片多一点、节点多一点,就容易顶到宿主限制。

所以我又加了第二种模式:流传输到本地(后面可能移除)。

使用时先在仓库根目录启动:

python3 tools/mastergo_relay_server.py

默认地址是:

http://127.0.0.1:8765

然后在 MasterGo 插件里选择「流传输到本地」。插件会把 JSON 和图片分块传给本地服务,Python 负责落盘,最后打包成:

mastergo2figma-relay-output/<transferId>.zip

这里没有引入额外 Python 依赖,用标准库就够了。这个服务的定位也很克制:只做接收、写文件、打包,不参与图层转换。

Figma 端:把 zip 还原成节点

ReceiveFromMasterGo 是接收端插件。

它运行在 Figma 里,用户上传 SendToFigma 导出的 zip 后,插件会按页面开始还原。大致过程是:

读取 manifest
  -> 读取资源和页面数据
  -> 创建 Figma Page
  -> 从 rootNodeIds 开始递归还原
  -> 创建 Frame / Group / Text / Vector 等节点
  -> 应用 fills、strokes、effects、constraints、layout 等属性
  -> 收尾处理 auto-layout、Group 坐标、字体和连接线

这部分最费时间。

导入不是简单地 createRectangle() 这么轻松。Figma 的节点创建、父子关系、auto-layout、Group、本地字体加载,都有自己的规则。很多属性不能在一开始就直接设置,必须先建完节点,再回头补。

比如 auto-layout。父级布局、子项布局、固定尺寸、transform,经常互相影响。如果恢复顺序不对,看起来就像“坐标明明写对了,导入后却歪了”。所以接收端里有延迟恢复逻辑,先把结构建出来,再分阶段补布局。

Group 也类似。Figma 的 Group 坐标和普通 Frame 不完全一样,子节点局部坐标需要在最后转换。如果直接按导出的相对坐标塞进去,视觉位置就可能偏。

这些坑都不大,但加在一起就很磨人。

做到一半的 .mg 解码

项目里还有一份 MG_DECODER.md,记录的是 MasterGo 原生 .mg 文件的解析过程。

.mg 本质上是一个 zip,里面有 documentmeta.json 和图片资源。麻烦的是,document 是 MasterGo 自己的二进制序列化格式。

我一开始能拿到一部分注入过的 JSON 数据,但这并不够。有些页面没有这类 JSON,只剩原生二进制。如果只读 JSON,导入 Figma 时就会出现页面缺失或者图层不完整。

虽然可以通过让 AI 对比二进制文件和 JSON,来实现对照,但是效果还是不行。所以暂时放弃,等 AI 能力强一些(上下文长度主要是)再试试

OOM 问题

这里是我踩得比较深的地方。

我原本以为,大文件失败主要是因为在插件 UI 里打 zip 太吃内存。于是我加了 Python 中继,让 UI 不再一次性拼完整 zip。

这确实有用,但没有彻底解决问题。

原因是 MasterGo 插件主线程目前不能直接 fetch 本地服务。数据必须先从插件主线程通过 mg.ui.postMessage 发给 UI,再由 UI 发给 Python。也就是说,即使 Python 在流式写盘,MasterGo 宿主内部还是要处理大量 JSON 和图片 chunk 的序列化、复制和排队。

这部分内存开销插件控制不了。

所以现在的判断是:

  • Python 中继能减少 UI 打包 zip 的峰值内存。
  • 如果 OOM 出现在 mg.ui.postMessage 的桥接层,它解决不了。
  • 特别大的页面还是建议拆开导出。

这听起来不够漂亮,但这是当前插件 API 限制下更接近事实的答案。

使用方式

直接去看 Github 吧

开源协议

项目目前使用 CC BY-NC-SA 4.0。

也就是:署名、非商业性使用、相同方式共享。

如果只是学习、研究、非商业分享,问题不大。如果要放进商业流程里,建议先看一下许可证,或者联系我确认授权方式。

写在最后

目前基本实现了所有类型图层的迁移,起码我自己作图习惯的图层是迁移没问题了。

不过还是会 TODO,比如还原后的 Instance 和 Component 目前还不知道怎么对应起来。当前是粗暴地处理成 Frame

项目还会继续迭代。如果你也遇到 MasterGo 到 Figma 的迁移问题,欢迎试试,也欢迎提 issue 或 PR。