在2023年初,我发布了一个名为Placemark的Figma插件,它可以让你在Figma中创建矢量地图,这是一款图形设计工具。从那时起,我一直在闲暇时维护着该插件,并且推出了另一个插件,Placemark Globe。
它们在某种程度上取得了一定的成功!根据Figma社区网站的数据,Placemark插件有11.3k用户,而Placemark Globe插件有1.5k用户。插件的反馈循环还可以 - 你可以看到你的插件获得了多少赞,以及有多少“用户”,但没有随时间变化的统计数据,因此我使用Observable构建了自己的插件仪表板。
但即使如此,我仍然不太清楚有多少人实际在使用它们。也许“用户”意味着唯一的安装 - 当然并不意味着活跃用户,因为我生成的图表只会“上升”,我知道活跃用户数量比那更不稳定。
沙盒化
我对Figma插件很着迷,因为沙盒化带来了一些挑战。插件本质上是第三方代码,可以访问用户的文档、信息,以及在某种程度上,他们的计算机。你希望插件既强大又安全。
Figma拥有顶级的工程团队,他们努力使这个工作。他们甚至曾经犯了一个重大错误,首先实施了一个基于Realms的解决方案,但很快就发现存在安全漏洞。
沙盒化是一个非常有趣的话题,因为每个人都想要,但真正成功的例子却很少。例如,当我在 iD上工作时,这是OpenStreetMap的地图编辑器,我们总是面临创建一个插件架构以与JOSM竞争的挑战,后者是一个基于Java的桌面应用程序,更加成熟。
在Observable中的沙盒化
Observable是沙盒化的一个很好的实例。Mike在我到达之前基本上已经解决了整个挑战:架构看起来像这样:
- 用户代码在带有
sandbox
属性的iframe中运行。顶层框架通过postMessage与iframe通信。 - iframe可以向顶层框架进行回传,用户代码可能会恶意发送自己的消息(没有“受信任的通信渠道”),因此来自iframe的任何消息都在其权限范围内受到限制。大多数情况下,iframe只是告诉顶层框架其内容的尺寸。
- 每个用户都有一个通配符子域,iframe内容托管在其中,因此cookie不会冲突。
这让我们走得很远 - 从安全的角度来看,Observable沙盒化基本上可以正常工作,但它也遇到了一些其他问题。例如,如果有人在Observable Notebook中编写了一个无限循环,它将使iframe进程的CPU占用率达到100%,他们将不得不使用“安全模式”版本的应用程序才能摆脱无限循环。浏览器供应商已经对此进行了一些调整,无论是否将iframe设置为独立进程,或与顶级页面共享,或者也许来自某个特定来源的所有iframe都有自己的进程。这是一个复杂的话题。
在Val Town中的沙盒化
我最近写了一篇关于Val Town的沙盒化之旅的长篇文章。简而言之,该产品最初是基于Node.js的vm模块和它的包装器启动的,但很快意识到这种方法没有未来:在Node.js进程内部进行沙盒化的所有尝试都失败了。
因此,我们转向了Ryan Dahl创建的项目Deno。Deno从一开始就具有许可模型,让你可以启用或禁用程序对资源的访问。因此,你最终可以做到像安装一个模块并在它尝试读取你的/etc/passwd
文件之前捕获它一样,并阻止
它这样做。
Val Town使用node-deno-vm启动一个单独的Deno进程,它公开一个WebSocket,接收用户代码,并使用Deno的权限模型运行它。这会带来一些开销,但除此之外效果非常好。
Deno非常酷,并且到目前为止一直运行良好。
在Figma中的沙盒化
Figma对沙盒化问题的最终解决方案是使用QuickJS,这是一个小巧的JavaScript引擎,可以编译为WASM。实际上,你可以使用QuickJS在JavaScript中运行JavaScript引擎。
因此,插件架构在iframe中运行插件的一部分,你可以在其中显示用户界面,另一部分在QuickJS中运行,该代码可以实际上读取和写入你的Figma文档。
现在,我已经编写了两个Figma插件,我可以说这是有效的。我遇到的最大问题是调试性能。
JavaScript的调试故事确实非常好。当你的代码出了问题时,大多数情况下你会得到一个清晰的错误消息,指明了问题出现的地方,你可以使用调试工具获取更多信息。我在2015年写过关于这个的文章。
当QuickJS在Figma中遇到问题时,你会得到真正晦涩的错误。我花了很多时间猜测我的插件出了什么问题,因为我所依赖的只是类似上面的堆栈跟踪。
更难量化的是,Figma插件的性能也不是很好。他们最近推出了动态加载,这应该改善一些边缘性能,但我的插件仍然比他们应有的运行速度慢。当然,我在制作地理空间插件,很容易忘记地理空间数据对系统的要求有其独特之处。地理空间工具处理的数据量比大多数绘图工具要多得多。
插件架构
我应该再次强调Figma拥有一个允许许多人编写插件的插件架构是多么了不起。这在网站上是非常罕见的。如果我试图命名另一个允许社区插件自由运行在前端的网站,我可能找不到。
我真的希望构建这种东西更容易!尽管人们对网络缺乏支付机制的讨论很多,但我们应该更多地关注的是,网络缺乏扩展性机制:我们基本上只有iframes作为启用插件架构的唯一安全原语,而它们还有很多不足之处。WebAssembly是未来(多年来一直是未来),但WebAssembly代码的分发和调试故事仍然处于初级阶段。
当然,沙盒化本身是一个难题。有一些成功的例子 - 在Lua中编写视频游戏脚本肯定是其中之一。如果普通网站可以定制和扩展,Web将会是一个完全不同且希望更好的地方。
其他插件编写体验
既然我已经对沙盒化发表了看法,那么Figma插件编写流程的其他方面呢?
非常好!发布新版本的Figma插件非常顺畅,只需几个点击,它就立即出现在社区网站上。Figma社区网站的成功非常明显,这里有很多内容。还有许多针对地图的插件,质量各不相同。
Placemark插件
注意:这篇文章涉及一系列不同但有关的话题,你可能已经注意到了。我可以将它们编辑成更连贯的内容,但我写作只是为了乐趣,也许阅读这些也是有趣的。
Placemark Figma插件是一个更先进的项目。它直接从Overpass Turbo获取地图数据,这是OpenStreetMap数据的一个API。相对于其他选项,这是相当有意义的:
- 从栅格瓦片源加载地图既会产生静态的栅格地图,无法进一步进行样式设置,也会引入另一层知识产权:像Mapbox或Maptiler这样的提供商的样式通常受版权保护。
- 从矢量瓦片源加载瓦片可能从长远来看是一个好主意,但矢量瓦片通常针对性能和轻量级优化,而不是
完整性。目前,使用我的插件,你可以真正获取整个OpenStreetMap数据模型,如果你愿意的话,甚至可以添加树木到地图上。
因此,从OpenStreetMap加载地图使得插件具有更强大和简单的功能,并且很明显,唯一需要的属性是OpenStreetMap标准归属,该插件默认添加(请保持沉默,喷子!)
它是开源的,因此你可以看看它是如何实现的。基本上,它实现了一个微小的静态地图渲染引擎,包括标签放置、样式设置、对OpenStreetMap的标记方案的一些理解等。它使用了d3的Mercator 地图投影实现,这在局部尺度上并没有太大差别。
该插件有两个主要的弱点:水和缩放级别。
它在渲染水域方面效果不佳。这是地图世界中一个非常棘手的问题:假设你正在渲染一幅显示海岸城镇的地图,海洋在右边。你怎么知道海洋在右边?你下载了整个巨大的海洋多边形吗?那会极大地减慢你的渲染过程。有一些有效的高效方法来解决这个问题,这些方法相当令人惊讶和酷:
绘制方式的方向非常重要!它们必须绘制,以便地形位于方式箭头的左侧,水位于右侧(在观察方向的方式箭头)。如果你认为这是围绕一个地区的地形描绘,比如说,一个岛屿,那么海岸线的方式应该是顺时针运行。 - 来自OpenStreetMap维基
然而,我还没有找到时间来智能地实现海岸线。它是开源的,所以如果你有灵感,我随时可以合并PR。
另一个问题只是缩放级别:为了渲染一个缩小的地图,我需要制作不同的Overpass Turbo查询,以便下载的数据量更加合理,但我还没有这样做。
Placemark Globe插件
Placemark Globe插件要简单得多:它只是渲染一个地球。它甚至还没有渲染标签。
我认为创建这个的动机是,通常情况下“这还不存在”,但也有技术上简单的东西仍然可能有用。总有一天,我会找到一些又简单、又有用、又有利可图的东西!
这个插件更多地使用了d3 - 实际上,没有d3的辛勤工作,它是不可能的。它的插件部分将SVG转换为Figma的文档模型和样式系统,这与SVG略有不同。并且使所有这些变得方便。当然,还有许多其他方法来生成矢量地球并将其引入Figma - 这只是一个简单的方法。
因为它显示边界,所以它使用了Visionscarto World Atlas项目的数据,这是自然地球的修改版,它以更普遍被接受的方式显示了一些有争议的边界。关于国际边界的地图制图有很多文章,我就不在这里总结了。
插件架构
最初我使用create-figma-plugin创建了Globe插件。我认为它有一些不错的模式。其他地方也有一些很好的插件开发资源。
基本上,我已经决定了对于Figma插件来说,React是过度的。如果你只有几个UI元素,那么几乎任何框架都是过度的。只需编写一些HTML和JavaScript,不要过度思考。我希望有一个独立的CSS框架,这样我就可以直接获得Figma美观的外观和暗模式支持。
维护这些Figma插件非常有趣。它让我满足了尝试新技术的欲望,并帮助我在地图世界中保持一点存在感,这仍然是我喜欢的事情。