前言
在 VS Code 开发工具中,可以在侧边栏中创建一个持久化的自定义视图。这个视图可以随着 VS Code 的不同窗口、标签页之间的切换而保持存在。
registerWebviewViewProvider
在插件的激活阶段,使用 vscode.window.registerWebviewViewProvider
方法来注册自定义视图的提供者。这个方法接受三个参数,如下:
- 唯一的视图 ID
- 用于自定义视图的 HTML 内容、事件处理等
- 可选的配置选项
当你注册一个 Webview 视图提供者时,需要提供一个回调函数来处理视图的创建和配置。这个回调函数就是 resolveWebviewView。
javascript
vscode.window.registerWebviewViewProvider("yourViewId", {
resolveWebviewView: (webviewView, context) => {
// 在这里设置自定义视图的 HTML 内容、事件处理等
webviewView.webview.html = '<h1>Hello from Webview!</h1>';
}
}, {
webviewOptions: {
retainContextWhenHidden: true
}
});
retainContextWhenHidden
是布尔类型,用于控制当 Webview 隐藏(不可见)时是否保留其上下文。当设置为 true 时,在用户切换到其他面板或关闭 Webview 时,Webview 的状态和内容将保持不变。这可以在某些情况下提供更好的用户体验,因为用户在返回到 Webview 时可以继续之前的操作,而不必从头开始。
自定义视图添加到活动栏
viewsContainers
用于定义自定义视图容器。 activitybar
位于编辑器侧边的垂直工具栏。活动栏提供了快速访问各种功能、面板和操作的图标按钮,使用户能够轻松地切换和执行不同的任务。其中参数如下:
- id:标识符
- title:标题
- icon:图标
views
配置用于定义自定义视图(Views)以及这些视图的属性和行为。这些视图可以包括内嵌的 Webview,以便在插件中显示自定义的 Web 内容、UI 界面等。其中参数解释如下:
- id:视图的唯一标识符,用于在扩展中引用这个视图。
- name:视图的名称,将在用户界面中显示。用户将通过这个名称来识别视图。
- type:视图的类型。在这里,设置为 "webview",表示在视图中使用 Webview 来显示自定义的 Web 内容。
通过使用 viewsContainers"
和 views
配置,可以创建自己的自定义视图和容器,将其添加到活动栏中,并与 Webview 结合以显示自定义的界面和内容。
javascript
"viewsContainers": {
"activitybar": [
{
"id": "wxRead-container",
"title": "wxRead",
"icon": "media/logo.png"
}
]
},
"views": {
"wxRead-container": [
{
"id": "wxRead-view",
"type": "webview",
"name": "wxRead"
}
]
}
结果展示如下
案例
我们自定义视图的时,简单的可以直接在registerWebviewViewProvider
的第二个参数进行设置,不过我们处理复杂的逻辑的时候,一般会封装一个视图,然后引入到extension.ts
中使用。
新建SidebarProvider.ts
文件
在构造函数中,传入扩展的根路径 _extensionUri
和扩展的上下文 context。通过 _context.globalState.get('Token')
获取之前存储的 token。
在 resolveWebviewView
方法中,配置 Webview 的选项,使其支持运行脚本和加载本地资源。
使用 webviewView.webview.onDidReceiveMessage
监听 Webview 内的消息,并根据不同的 data.command
执行不同的操作。在这个示例中,包括处理登录和登出请求。
loginRequest
方法用于发送登录请求,通过 axios 发送 POST 请求到服务器,处理服务器返回的数据。
_getHtmlForWebview
方法用于生成 Webview 的 HTML 内容。它通过 asWebviewUri 方法获取资源文件的 URI,设置 Content Security Policy(CSP),并嵌入需要加载的脚本和样式。
其中某些参数的解释:
-
enableScripts: true: 这个选项设置为 true,允许在 Webview 中运行 JavaScript 脚本。如果你的 Webview 需要执行一些交互式操作或展示动态内容,你需要将这个选项设置为 true。
-
localResourceRoots: [this._extensionUri]: localResourceRoots 是一个数组,指定了可以从本地加载的资源的根路径。在这个代码中,this._extensionUri 是扩展的根路径 URI,这表示 Webview 可以从扩展的根路径加载本地资源。
_getHtmlForWebview方法中:
styleResetUri、styleVSCodeUri、scriptUri 和 styleMainUri
:这些是通过webview.asWebviewUri
方法获取的资源文件的 URI。这些 URI 是扩展中的 CSS 样式表和 JavaScript 脚本的位置。
nonce
:这是一个用于 CSP 的 nonce 值,用于限制只有特定 nonce 值的脚本能够被执行。
<meta http-equiv="Content-Security-Policy">
:这是 Content Security Policy(CSP)的设置,用于指定允许加载的资源和脚本。它限制了从 https 或扩展目录加载的图像,只允许特定 nonce 值的脚本被执行。
最后,返回的 HTML 包括引用了所需资源的 <link>
标签和 <script>
标签。其中,<script>
标签引用了你的编译后的 JavaScript 脚本,nonce 值用于安全性,以确保只有符合 nonce 条件的脚本被执行。
javascript
import * as vscode from "vscode";
import { getNonce } from "./getNonce";
import axios from "axios"
import { error } from "console";
export class SidebarProvider implements vscode.WebviewViewProvider {
_view?: vscode.WebviewView;//存储 Webview 视图
private _context: vscode.ExtensionContext; // 存储扩展上下文对象
private _token: string | undefined;// 存储token
constructor(private readonly _extensionUri: vscode.Uri, context: vscode.ExtensionContext) {
this._context = context;
this._token = this._context.globalState.get('Token');
}
public resolveWebviewView(webviewView: vscode.WebviewView) {
this._view = webviewView;
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this._extensionUri],
};
webviewView.webview.onDidReceiveMessage(async (data) => {
console.log(data, data.command, 'command1111111111')
switch (data.command) {
case "login": {
let obj;
if (data && data.loginname) {
obj = { username: data.loginname, password: data.password }
} else {
obj = { username: this._userInfo?.loginname, password: this._userInfo?.password }
}
console.log(this._userInfo, 'this._userInfo')
this.loginRequest(obj, webviewView);
break;
}
case "logout": {
axios.post(
`/logout`,
{ username: this._userInfo?.username, password: this._userInfo?.password },
{
headers: {
Authorization: `Bearer ${this._token}`, // 假设 token 的类型是 Bearer token
},
}
).then((res) => {
console.log(res, "res")
if (res.data.code === 200) {
this._isLoggedIn = false;
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview)//用于生成 Webview 的 HTML 内容
this._context.globalState.update('Token', '');
this._token = '';
vscode.commands.executeCommand('extension.logoutSuccess');
}
if (res.data.msg) vscode.window.showInformationMessage(res.data.msg);
}).catch(error => {
vscode.window.showErrorMessage("服务器连接错误!");
})
break;
}
}
});
}
public loginRequest(data: any, webviewView: vscode.WebviewView) {
axios.post(`/login`, data)
.then(res => {
if (res.data.code === 200) {
webviewView.webview.html = this._getHtmlHome(webviewView.webview);//用于生成 Webview 的 HTML 内容
webviewView.webview.postMessage({
command: 'loginResponse',
success: true,
message: { ...res.data, imageUri, password,loginname: data.username }
});
this._token = res.data.token;
vscode.commands.executeCommand('extension.loginSuccess', res.data);
vscode.window.showInformationMessage(res.data.msg);
} else {
vscode.window.showErrorMessage(res.data.msg);
}
}).catch(error => {
vscode.window.showErrorMessage("服务器连接错误!");
})
}
public revive(panel: vscode.WebviewView) {
this._view = panel;//将传入的 Webview 视图存储到 _view 成员变量中,用于后续操作
}
// 在其他地方调用此方法来设置缓存的数据
public setData(data: any) {
}
// 在其他地方调用此方法来获取缓存的数据
public getData() {
}
//用于生成 Webview 的 HTML 内容
private _getHtmlForWebview(webview: vscode.Webview) {
//asWebviewUri获取资源文件的 URI
const styleResetUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "reset.css")
);
const styleVSCodeUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vscode.css")
);
const scriptUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "out", "compiled/HelloWorld.js")
);
const styleMainUri = webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "out", "compiled/HelloWorld.css")
);
// Use a nonce to only allow a specific script to be run.
const nonce = getNonce();
let a = 0;
a++;
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--
Use a content security policy to only allow loading images from https or from our extension directory,
and only allow scripts that have a specific nonce.
-->
<meta http-equiv="Content-Security-Policy" content=" img-src https: data:; style-src 'unsafe-inline' ${webview.cspSource
}; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleResetUri}" rel="stylesheet">
<link href="${styleVSCodeUri}" rel="stylesheet">
<link href="${styleMainUri}" rel="stylesheet">
</head>
<body>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
}
}
extension.ts中引入
import SidebarProvider from './SidebarProvider'
:这里导入自定义 SidebarProvider 类,用于管理 Webview 视图的创建和交互。
创建 readerViewProvider
实例:通过 new SidebarProvider(context.extensionUri, context)
创建了一个 SidebarProvider
实例,将扩展的根路径和上下文对象传递给它。
使用 vscode.window.registerWebviewViewProvider
注册 Webview 视图提供者:这行代码注册了你的 readerViewProvider
实例作为 'wxRead-view'
标识符的 Webview 视图提供者。同时,通过传递一个配置对象,你设置了 Webview 的选项,其中 retainContextWhenHidden
设置为 true,以便在 Webview 隐藏时保留其上下文。
其中部分参数解释如下:
-
context
:用于传递上下文信息和提供功能的对象。 -
context.extensionUri
:用于获取当前扩展的根路径的 Uniform Resource Identifier (URI)。这个 URI 表示扩展在文件系统中的位置,可以用于引用扩展中的资源文件、图标、样式表等。具体如下:
javascript
import SidebarProvider from './SidebarProvider';
const readerViewProvider =new SidebarProvider(context.extensionUri, context)
vscode.window.registerWebviewViewProvider('wxRead-view', readerViewProvider, {
webviewOptions: {
retainContextWhenHidden: true,
},
});