VSCode Webview 中如何发送远程请求

791 阅读3分钟

VS Code 允许扩展程序(Extension)使用 Webview API 在其中创建完全可自定义的视图。Webview 可用于构建复杂的用户界面,超出 VS Code 原生 API 的支持范围。

在webview开发过程中,当页面涉及一些复杂功能时(例如AI对话、翻译等),避免不了远程接口获取数据。本文就来聊聊,如何在VS Code webview中发送远程请求。

一、同源策略限制

我们知道 VSCode 是基于 Electron开发的。

Electron.js: 使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架,基于 Chromium 和 Node.js

而Webview 本质上就是嵌入在 VSCode 扩展中的 iframe。那么在 Webview 中直接发送远程请求,势必会受到同源策略的限制。

同源策略:用于限制一个的文档或者它加载的脚本如何能与另一个源的资源进行交互。

同源:两个 URL 的协议端口主机都相同

如下,假设我在 webview 中向 http://xxx.com/chat发送请求,就会报跨域的error。

await axios.post(
    'http://xxx.com/chat', 
    {}
)						

image.png

二、VS Code 与 webview 的通信

如上所述,受同源策略的限制,在 Webview 中直接发送远程请求是行不通的。

既然这样,那我们就换一种思路,是否可以在 VS Code 扩展去发送远程请求,获取到数据然后在传递给 webview 呢?答案是肯定的,因为 VS Code 扩展封装了一套与 webview 页面通信的方法。

假设我们创建了如下 WebviewPanel

const panel = window.createWebviewPanel(
    'viewType', // view类型
    'title', // 视图标题
    ViewColumn.One, // 面板打开位置
    {
        // 允许 webview 中的脚本
        enableScripts: true,
    }
);

注意:Webview 中禁用 JavaScript,所以需要传入 enableScripts: true 参数启用。

2.1 将消息从扩展程序传递到 webview

  • 扩展可以使用 webview.postMessage() 将数据发送到其 webview。
panel.webview.postMessage();
  • webview 内部使用 window.addEventListener('message', event => { ... }) 来处理消息。
<script>
    window.addEventListener('message', event => {
    ...
    });
</script>

2.2 将消息从 webview 传递到扩展程序

  • webview 内调用 acquireVsCodeApi 访问 VS Code API 对象,然后调用VS Code API 对象上的 postMessage 函数,发送消息给扩展。
<script>
    const vscode = acquireVsCodeApi();
    vscode.postMessage();
</script>
  • VS Code 扩展可以通过监听 onDidReceiveMessage 事件来处理 webview 中传来的消息
panel.webview.onDidReceiveMessage((message) => {
    ...
});

有关 VS Code 扩展与 Webview 通信的具体示例可见官方文档

2.3 发送远程请求

既然了解了 VS Code 扩展与 webview 通信的方式,那下面我们就看看如何在 webview 中请求远程数据。

假设我们想要请求 http:xxx.xxx.com/chat 接口的数据,且传递给接口的请求体为 {question: 'test'}。具体操作步骤如下:

  1. webview 中发送信息告知 VS Code 扩展请求远程接口数据
vscode.postMessage({
    command: 'fetchChat',
    data: {question: 'test'}
})
  1. VS Code 扩展处理接收到的信息,并向指定接口发送请求,获取到接口数据后,再将接口数据回传到 webview。
panel.webview.onDidReceiveMessage((message) => {
    const { command, data } = message ?? {};
    
    if (command === 'fetchChat') {
        const res = await axios.post('http:xxx.xxx.com/chat', data);
        vscode.postMessage({
            command: 'fetchChatResult',
            data: res.data
        })
    }
});
  1. webview 中监听 message 事件,接收 VS Code 扩展传来的接口数据:
window.addEventListener('message', (e) => {
    const {command, data} = e.data;
    if (command === 'fetchChatResult') {
        // 对于接口返回数据的相关操作
        ...
    }
});

总的来说:

  • webview 中执行的操作是:告知 VS Code 扩展想要请求哪个接口,并监听message事件,接收 VS Code 扩展请求接口返回的数据
<script>
    window.addEventListener('message', (e) => {
        const {command, data} = e.data;
        if (command === 'fetchChatResult') {
            // 对于接口返回数据的相关操作
            ...
        }
    });
    
    ...
    vscode.postMessage({
        command: 'fetchChat',
        data: {question: 'test'}
    })
</script>
  • VS Code 中执行的操作是:根据 webview 传来的消息发送远程请求,并将接口返回的数据回传给 webview

本文到这里就结束了,如有错误,请大佬们指正。