切换至深色主题
大型前端系统插件生态容器
、插件完整生命周期(宿主全权管理)
宿主的 PluginManager 统一管控:
- 解析阶段:读取 manifest,校验插件 ID、版本、权限,检查是否和宿主版本兼容
- 实例化:创建沙箱(iframe/worker),注入 SDK 代理对象
- 激活 activate:调用插件
activate();插件注册命令、事件监听 - 运行期:插件通过消息总线和宿主交互;宿主监听插件异常
- 停用 deactivate:卸载插件,调用
deactivate(),清理事件订阅、销毁沙箱 - 销毁:销毁 iframe/worker,释放内存,清除所有插件状态
宿主强制捕获插件沙箱内异常:插件报错不能崩溃主应用,只会标记插件异常、弹窗提示。
独立域 iframe
Worker
Figma 当年放弃 iframe 的两个硬数据是——消息往返 0.1ms(约 1000 次/秒),而大型文件整体序列化要 14 秒——所以它把 QuickJS 编译成 WASM 在主线程内跑独立 JS 虚拟机,UI 才放 null-origin iframe。大多数业务系统答案相反,异步够用,iframe/Worker 更划算。
核心目标:安全隔离 + 能力受控暴露 + 统一插件生命周期 + 外部开发者可开发、可分发、可安装
- 宿主主应用(你的大型前端系统)作为容器;
- 插件运行在隔离沙箱,不能直接访问宿主 DOM / 全局变量;
- 宿主提供一套标准化 API 能力层,插件只能通过这层 API 调用平台能力;
- 宿主管理插件生命周期、权限、消息通信、资源加载。
整体架构分层(自下而上)
关键原则:插件永远不能直接调用宿主内部代码,所有能力必须经过代理层转发。和 Figma 一样,插件只能调用
figma.xxx;VSCode 插件只能调用vscode.xxx。
宿主主应用(你的大型前端系统)
├── 插件容器核心层(Plugin Runtime Container)
│ ├── 插件管理器 PluginManager
│ ├── 沙箱运行时 Sandbox
│ ├── 消息通信总线 MessageBus
│ ├── 生命周期管理器 Lifecycle
│ └── 权限控制中心 Permission
├── 平台能力代理层(Platform API / 插件SDK)⭐核心
│ ├── 宿主能力封装(页面、组件、数据、画布、命令、事件)
│ ├── 类型定义(d.ts,给外部开发者)
│ └── 版本兼容适配器
├── 宿主内部业务层(原有系统业务逻辑,**禁止直接暴露给插件**)
└── 插件市场/后台(插件上传、审核、版本管理、安装)
外部插件:由第三方开发者编写,只依赖平台SDK,运行在沙箱内插件沙箱方案(前端最重要的安全问题)
方案 1:iframe 沙箱(最推荐,对标 Figma)
- 每个插件独立 iframe,设置
sandbox属性,禁用脚本以外权限 - iframe 内是独立上下文,window、DOM 完全隔离,插件无法直接篡改宿主页面 DOM
- 通信:
postMessage作为消息总线,所有调用都序列化消息传递 - 优点:隔离性最好,简单稳定;插件崩溃不会搞挂主应用
- 缺点:iframe 实例多的时候有内存开销;样式隔离;不能共享 WASM 等资源
Figma 就是这套:插件代码跑在 iframe,
figma对象是宿主注入的代理对象,插件调用方法只是发 postMessage。
方案 2:WebWorker 沙箱(适合纯计算、无 UI 插件)
- 插件代码跑在 Worker,没有 DOM
- 适合:数据处理、导出、校验、批量计算类插件
- 局限性:插件不能直接渲染 UI;如果插件需要弹窗 / 面板,UI 必须由宿主渲染
方案 3:基于 ES Module + 动态 import + 代理(轻量沙箱,慎用)
不推荐作为主方案。原生
import无法完全隔离全局对象,容易逃逸,适合内部可信插件,不适合开放给外部第三方。
运行时层:四种沙箱的取舍与"可替换"原则
方案 隔离强度 同步访问宿主对象 性能开销 生态与维护成本 最适用场景 主线程 QuickJS-in-WASM 高:独立线性内存,无浏览器 API,对象表示与宿主完全不同,不可能混淆内外对象 可以,宿主注入函数可同步调用 无 JIT,解释执行;酷家乐公开分享称相对 V8 慢 30–40 倍、经 WASM 后约百倍(单一来源,定性) 高:跨 VM 绑定、句柄生命周期、调试困难 编辑器/画布类,需同步高频读写文档 SES Compartment 中高:lockdown 冻结 intrinsics 防原型污染 可以,同 realm 同线程 冻结 intrinsics + with/Proxy 作用域开销,官方未公布百分比 中:需审计每个 endowment 同线程插件但信任度较高 Web Worker 中:无 DOM、独立线程,可 terminate() 不可以,异步 MessagePort 通信与结构化克隆 低 计算密集、可异步 独立域/null-origin iframe 高:浏览器进程级隔离,null origin 下 CORS 天然拒绝 不可以,postMessage 通信与序列化;大对象序列化可能极慢 低,生态最成熟 插件 UI 面板、异步业务插件 WASM/WASI(Extism、Javy) 高:能力式,默认无 syscall 不可以(除非宿主逐个授予 import) 接近原生,可设 timeout_ms/maxPages 中:需用 Rust/Go 等写插件 批处理、服务端函数
能力层:API 网关与能力清单
能力域 风险 建议形态 权限要求 只读查询(当前文档/选中节点/视口) 低 快照 + 句柄,异步或同步均可 默认授予 写入修改(增删改节点/属性) 高 必须走事务层,单事务节点数与深度上限 声明 + 首次使用时授权 用户身份(currentUser) 中 只给最小字段,不给 token permissions: currentuser 本地持久化(clientStorage) 中 按插件命名空间分区,卸载即清 默认授予,有容量上限 网络请求 高 manifest 声明 allowedDomains,宿主 CSP 强制;高安全场景经宿主出口代理注入凭据 声明域名 + 公示理由 UI(面板、模态、通知) 中 宿主预留容器,禁自绘全屏 默认授予 导出/下载、剪贴板、外链 中高 需用户手势,宿主代执行 运行时申请 宿主主窗口 DOM 极高 永不开放 禁止
落地路线图
分五个阶段推进,每个阶段都能独立交付价值。 第一阶段做内核可插拔化:把状态读写收敛到命令/事务层,把撤销栈与协同 origin 机制做扎实。这一步不给外部插件用,但它是后面一切的地基,内部功能先以插件形式重写(吃自己的狗粮)。 第二阶段只开放只读 API + UI 面板,运行时用独立域 iframe,通信用 JSON-RPC。先只读后写入,是所有成熟生态的共同节奏。 第三阶段开放写入,引入事务校验、配额与权限中间件,把 API 网关做成唯一出口。 第四阶段按真实瓶颈决定要不要上同步运行时:只有当出现"插件必须高频同步读写文档模型"的场景时,才引入 QuickJS-in-WASM 或 SES Compartment,并且把它设计成可开关、可回退的第二个运行时,而不是替换现有 iframe 路径。 第五阶段做生态运营:市场、签名分级、审核流程、脚手架与文档站、灰度与可观测性闭环。
平台能力代理层(Plugin SDK,对标 figma /vscode)
// 外部插件,只依赖 @your-platform/sdk
import { platform } from "@your-platform/sdk";
// 插件激活
export function activate() {
// 调用宿主能力:读取画布数据
const nodes = platform.canvas.getSelection();
// 注册宿主命令
platform.commands.register("my-plugin.hello", () => {
platform.ui.showToast("插件运行成功");
});
}
export function deactivate() {
// 插件卸载清理
}可以对外暴露的能力分类(参考 Figma/VSCode)
- 核心数据能力
- 获取 / 修改画布、元素、页面数据
- 查询选中项、创建节点、删除节点
- 读取项目元数据
- UI 能力(宿主提供基础 UI 组件)
- 弹窗、toast、通知、侧边面板、模态框
- 宿主提供基础组件(按钮、输入框、下拉),插件调用 API 渲染,插件不能直接渲染 DOM 到宿主页面
- 如果插件需要自定义 UI:在 iframe 内部渲染自己的页面
- 命令与快捷键
- 注册命令、绑定快捷键,宿主菜单可以挂载插件命令
- 事件订阅
- 监听宿主事件:选中变更、页面切换、保存事件
- 文件 / 导出能力
- 导出图片、JSON、下载文件(宿主代理文件读写)
- 存储能力
- 插件本地存储:隔离存储,每个插件独立存储空间,不能互相访问
红线:不要把宿主内部的状态库、组件实例直接暴露出去。内部随便重构,只要 SDK 接口不变,外部插件不用改。