跳到主要内容

扩展和页面怎么通信,消息协议怎么设计?

结论​

两边只能用消息通信。扩展调用 webview.postMessage,页面用 acquireVsCodeApi().postMessage。消息是可结构化克隆的数据,不是函数,也不是类实例。协议要有类型字段,并且能对上请求和响应。

展开​

扩展侧:

panel.webview.onDidReceiveMessage((msg) => {
if (msg.type === "ready") {
panel.webview.postMessage({ type: "init", payload: state });
}
});

页面侧:

const vscode = acquireVsCodeApi();
vscode.postMessage({ type: "ready" });
window.addEventListener("message", (event) => {
// 按 event.data.type 分发
});

acquireVsCodeApi() 在一个页面里只能调用一次,要自己存起来。

协议建议:

  • 每条消息都有 type 字符串,例如 ready、init、save、select。
  • 需要对应响应时加 requestId。页面不要假设下一条消息就是自己的回复,用户操作会交错。
  • 载荷用普通对象。Uri 先变成 toString(),类实例过不去。
  • 大列表分页或增量推送。一次性把整个仓库文件内容推进页面会卡。
  • 页面发来的类型在扩展侧用联合类型收窄,未知类型直接忽略。

页面还没 ready 之前发出的 postMessage 会丢。扩展应等页面声明就绪,或在设置 HTML 之后重发当前状态。

监听器要随面板释放而取消,放进 subscriptions 或在 onDidDispose 里解开。

容易讲错的点​

  • 消息不是 RPC 框架。不约定 requestId 就会把 A 请求的结果画到 B 的界面上。
  • 不要把命令字符串直接从页面送去 executeCommand 而不做白名单。页面内容可能被工作区数据污染。
  • 扩展宿主和工作区如果在远端,消息仍然只到「持有这个 Webview 的那份扩展」。别假设远端进程能直接碰到这个页面对象。