扩展和页面怎么通信,消息协议怎么设计?
结论
两边只能用消息通信。扩展调用 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 的那份扩展」。别假设远端进程能直接碰到这个页面对象。