什么是 VSCode Webview

在VSCode中,仅靠原生插件API,无法做到自定义交互页面,这时需要 Webview 来嵌入自定义 Web 应用程序。使用Webview可以构建复杂的、支持各种操作的用户界面。Webview就像一个 iframe 一样,无法与外界(VSCode主界面)直接交互,需要使用acquireVsCodeApi特殊方法,这不是本文讨论的内容,不再深入。

显示一个 Webview 界面有两种方法,这篇文章主要说明第二个。

  1. 直接创建一个 Webview 标签页
  2. 将 Webview 添加进视图容器

在使用Webview前,你需要考虑几个问题:

  • 你的webview是否会带来足够的用户价值,以作为提高资源占用的交换?
  • 你的webview是否设计合理、美观?
  • 这个功能必须放在VSCode中吗,能否作为单独的网页?

构建 Webview Provider

顾名思义,Webview提供者,是提供webview视图的类,为谁提供?当然是视图容器(viewcontainer),视图放在视图容器内

这个类继承自接口WebviewViewProvider,该接口只有一个定义resolveWebviewView。在这个类中,还额外保存了extensionUri,以供后续定位文件。

主逻辑

"src/panels/someWebviewProvider.ts"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { CancellationToken, WebviewView, WebviewViewProvider, WebviewViewResolveContext, Webview, Uri } from "vscode";

export class SomeWebviewProvider implements WebviewViewProvider {

public constructor(
private readonly _extensionUri: Uri,
) { }

resolveWebviewView(
webviewView: WebviewView,
context: WebviewViewResolveContext,
token: CancellationToken
) {
throw new Error("Not Implemented");
}
}

Webview 的独特标识符

另外,建议添加一个字段来表明此 Webview 的独特类型。这个类型在最后一步 添加插件贡献 处会用到。

1
public static readonly viewType = 'example-extension.webview.test'; // 随意命名,在最后一步要用到

resolveWebviewView

这个方法的作用是,在视图(View)可见前解析Webview内容,这个webviewView参数就是视图。

至于解析,就是放进去HTML啦,直接为webviewView.webview.html赋值!

在这一段代码中,还可以

  • 使用onDidReceiveMessage和vscode通信
  • 更改webview的设置
"src/panels/someWebviewProvider.ts"
1
2
3
4
5
6
7
8
9
10
webviewView.webview.options = {
// Allow scripts in the webview
enableScripts: true,

localResourceRoots: [
this._extensionUri,
]
};

webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);

静态资源

出于安全考虑,Webview不能直接访问本地资源,想要加载静态资源,必须通过特殊的vscode-resource:协议,网页里面所有的静态资源都要转换成这种格式,否则无法被正常加载。我推荐封装一个工具函数:

"src/utilities/getUri.ts"
1
2
3
4
5
import { Uri, Webview } from "vscode";

export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) {
return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList));
}

我们使用 getUri(webview, this._extensionUri, ["out", "webview.js"]) 获取到了项目目录中的/out/webview.js,这是webview的具体逻辑。这 js 通常由 ts 编译而来,以 esbuild 为例:

"esbuild.js"
1
2
3
4
5
6
7
8
9
const webviewConfig = {
bundle: true,
minify: process.env.NODE_ENV === "production",
sourcemap: process.env.NODE_ENV !== "production",
target: "es2020",
format: "esm",
entryPoints: ["./src/webview/main.ts"], // 源代码文件位置
outfile: "./out/webview.js", // 输出位置
};

_getHtmlForWebview

解析资源路径,然后返回字符串HTML。getUrigetNonce为工具函数,下文有提及。

"src/panels/someWebviewProvider.ts"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private _getHtmlForWebview(webview: Webview) {

const webviewUri = getUri(webview, this._extensionUri, ["out", "webview.js"]);
const cssUri = getUri(webview, this._extensionUri, ["out", "style.css"]);
const nonce = getNonce();

return /*html*/ `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World!</title>
<link rel="stylesheet" href="${cssUri}">
</head>
<body>
<script type="module" nonce="${nonce}" src="${webviewUri}"></script>
</body>
</html>
`;
}

一些无关痛痒的工具函数,上文用到

"src/utilities/getNonce.ts"
1
2
3
4
5
6
7
8
export function getNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

注册

在插件的激活方法中注册 Provider:

"src/extension.js"
1
2
3
4
5
6
7
8
9
10
export function activate(context: ExtensionContext) {

const webview = window.registerWebviewViewProvider(
SomeWebviewProvider.viewType,
new SomeWebviewProvider(context.extensionUri)
);

context.subscriptions.push(webview);
}

定义 Contributes 插件贡献

与其翻译成插件贡献,不如说是插件的功能,注册了哪些命令啊,提供了什么视图……

pakage.json中指定插件的“贡献”:

  • viewsContainers 视图容器,可以被放置在侧边栏,就像“资源管理器”、“搜索”和“插件”一样。
  • views 视图,为上面定义的视图容器,添加视图,就像“资源管理器”中的“大纲”、“时间线”等小栏目。
"package.json"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "example-extension-container", // 容器的id,下面要在这个容器内添加 webview 视图
"title": "Example",
"icon": "./assets/extension-icon.png"
}
]
},
"views": {
"example-extension-container": [
{
"type": "webview",
"id": "example-extension.webview.test", // 刚刚定义的独特标识符
"name": "View 1",
"icon": "./assets/extension-icon.png"
}
]
}
},

萌ICP备20229066 | Build by C2iCs | Powered by Hexo and Stellar 1.27.0
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本“页面”访问 次 | 👀总访问 次 | 🍖总访客

开往-友链接力