本文介绍如何在 VS Code 插件的 webview 中加载本地的资源文件,并如何使用 VS Code 自身的 UI 来实现用户视觉体验的一致。
背景
最近想做一个 VS Code 的插件用来简便我使用 VS Code 来编辑 Markdown 博客的体验,在设计插件的过程中,因为需要在 webview 界面中使用到下拉框,想到为了节省插件大小,并考虑到用户体验一致性,故需要使用 VS Code 自身的 UI 库。
寻找蛛丝马迹:获取安装目录
因为我不清楚到底要如何去做,就先自己探索。先打开了 VS Code 的开发人员工具进行元素审查,看到是 workbench.desktop.main.css
这个文件。
<link rel="stylesheet" type="text/css" data-name="vs/workbench/workbench.desktop.main" href="vscode-file://vscode-app/d:/Program%20Files/Microsoft%20VS%20Code/resources/app/out/vs/workbench/workbench.desktop.main.css">
根据以上信息我们可以得知,其实际目录需要特殊的魔法去获取,因为引用的路径是安装目录的位置,不同电脑的肯定是不一样的。
这里我们就前往 VS Code 的仓库去扒拉源代码,最后虽然根据 vs/workbench/workbench.desktop.main
找到了一些线索,但是不堪大用啊,还是需要找到安装目录才行。几番折腾发现源码里获取软件版本信息 product.json
的方法,原来里面有环境变量啊。
const product = JSON.parse(fs.readFileSync(path.join(env.appRoot, 'product.json'), { encoding: 'utf-8' }));
那么,若在插件中要想获取到安装目录,直接使用下面的方法就好了:
const vscodeInstallPath = vscode.env.appRoot;
一波三折,并不顺利
首先在插件中,我们获取 html 内容后替换占位符信息如下:
const appRoot = vscode.Uri.file(vscode.env.appRoot);
this.view.webview.html = html.replace(/\[insert-vscode-root\]/g,`${appRoot}`);
通过替换 webview 页面的引用信息,实现动态的 workbench.desktop.main.css
资源引用后,不出意外的出了意外了:
虽然看起来引用的路径是没有什么问题了,但是结果却令人糟心啊:
Not allowed to load local resource
期间我尝试了 vscode-file://vscode-app/
协议直接拼接 appRoot
和 asWebviewUri
但是并没有成功获取到,都是网络错误。
asWebviewUri
看起来似乎有些靠谱,观察开发人员工具中的网络请求似乎很多都是这样类似的连接:
https://file+.vscode-resource.vscode-cdn.net/c%3A/Users/sangsq/AppData/Local/Programs/Microsoft%20VS%20Code/resources/app/out/vs/workbench/workbench.desktop.main.css
不过这个 net::ERR_ABORTED 401
却是让人高兴不起来,貌似权限问题。此时凌晨已至,夜寒露重,故搜索了一遍 Stackoverflow 后,便提了一个问题关机睡觉。
继续探索
在没有获得到答案后,还是要靠自己。认真看看官方文档,在扩展指南的加载本地内容中得到了一些答案。
出于安全原因,Webview 运行在隔离的环境中,无法直接访问本地资源。想从扩展加载图片、样式表或其他资源,或者从用户当前的工作区加载任何内容,必须使用 Webview.asWebviewUri
来转换为一个特殊的 URI 来使用。
前面已经提到我用过了 Webview.asWebviewUri
但是还有一些其他限制,默认情况下 Webview 只能访问以下位置的资源:
- 扩展程序的安装目录
- 用户当前的活动工作区
使用 WebviewOptions.localResourceRoots
可以允许访问其他本地资源。
createWebviewPanel
方法的第4个参数 webviewOptions.localResourceRoots
是一个只读数组,默认情况就是之前提的扩展程序的安装目录和用户当前的活动工作区。当然你也可以设置成空数组,这样就禁止访问任何本地资源。
这样在创建时稍作修改就可以了。
this.view = vscode.window.createWebviewPanel(
"blogPreview",
"Markdown Blog Preview",
vscode.ViewColumn.Two,
{
enableScripts: true,
localResourceRoots: [
vscode.Uri.file(path.join(context.extensionPath, "html")),
vscode.Uri.file(vscode.env.appRoot)
]
}
);
在 html 内容中替换指定的字符串:
<link rel="stylesheet" type="text/css" href="[insert-vscode-css]">
const cssfile = vscode.Uri.file(path.join(vscode.env.appRoot,"out/vs/workbench/workbench.desktop.main.css"));
const cssurl = this.view.webview.asWebviewUri(cssfile);
this.view.webview.html = html.replace(/\[insert-vscode-css\]/g,`${cssurl}`);
最后成果
在 workbench 目录中还存在有其他可用资源,这里仅对 select 的效果做个演示。
未使用 VS Code 的 UI 时:
使用 VS Code 的 UI 时:
另外在 Webview 的 html 显示中会被加入当前的样式和主题信息,需要自行进行适配调整。
<html lang="zh-CN" style="--vscode-font-family:………略,太多了………">
<body role="document" class="vscode-dark" data-vscode-theme-kind="vscode-dark" data-vscode-theme-name="Dark+ (default dark)" data-vscode-theme-id="Default Dark+">