VS Code Webview API
webview API为开发者提供了完全自定义视图的能力,例如内置的Markdown插件使用了webview渲染Markdown预览文件。Webview也能用于构建比VS Code原生API支持构建的更加复杂的用户交互界面。
可以把webview看成是VS Code中的iframe,它可以渲染几乎全部的HTML内容,它通过消息机制和插件通信。这样的自由度令我们的webview非常强劲并将插件的潜力提升到了新的高度。
我应该用webview吗?
webview虽然很赞,但是我们应该节制地使用这个功能——比如当VS Code原生API不够用时。Webview重度依赖资源,所以它脱离插件的进程而单独运行在其他环境中。在VS Code中使用设计不良的webview会让用户抓狂。
在使用webview之前,请作以下考虑:
- 这个功能真的需要VS Code来提供吗?分离成一个应用或者网站会不会更好?
- webview是实现这个特性的最后方案吗?VS Code原生API是否能达到同样的目的呢?
- 你的webview所牺牲的高资源占用是否能换得同样的用户价值?
请记住:不要因为能使用webview而滥用webview。相反,如果你有充足的理由和自信,那么本篇教程对你来说会非常有用,现在就让我们开始吧。
Webviews API 基础
为了解释webviewAPI,我们先构建一个简单的Cat Coding插件。这个插件会用一个webview显示猫写代码的gif。随着我们不断了解API,我们会不断地给插件添加功能,包括我们的猫写了多少行代码的计数跟踪器,如果猫猫写出了bug还会有一个提示弹出框。
这是Cat Coding插件的第一版package.json,你可以在这里找到完整的代码。我们的第一版插件提供了一个命令,叫做catCoding.start。当用户从命令面板调用这个Cat Coding: Start new cat coding session 。
{"name": "cat-coding","description": "Cat Coding","version": "0.0.1","publisher": "bierner","engines": {"vscode": "^1.23.0"},"activationEvents": ["onCommand:catCoding.start"],"main": "./out/src/extension","contributes": {"commands": [{"command": "catCoding.start","title": "Start new cat coding session","category": "Cat Coding"}]},"scripts": {"vscode:prepublish": "tsc -p ./","compile": "tsc -watch -p ./","postinstall": "node ./node_modules/vscode/bin/install"},"dependencies": {"vscode": "*"},"devDependencies": {"@types/node": "^9.4.6","typescript": "^2.8.3"}}
现在让我们实现catCoding.start命令,在我们的主文件中,像下面这样注册一个基础的webview:
import * as vscode from 'vscode';export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {// 创建并显示新的webviewconst panel = vscode.window.createWebviewPanel('catCoding', // 只供内部使用,这个webview的标识"Cat Coding", // 给用户显示的面板标题vscode.ViewColumn.One, // 给新的webview面板一个编辑器视图{ } // Webview选项。我们稍后会用上。);}));}
vscode.window.createWebviewPanel函数创建并在编辑区展示了一个webview,下图显示了如果你试着运行catCoding.start命令会显示的东西:

我们的命令以正确的标题打开了一个新的webview面板,但是没有任何内容!要想把我们的猫加到这个面板里面,我们需要webview.html设置HTML内容。
import * as vscode from 'vscode';export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {// 创建和显示webviewconst panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, { });// 设置HTML内容panel.webview.html = getWebviewContent();}));}function getWebviewContent() {return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cat Coding</title></head><body><img src="https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif" width="300" /></body></html>`;}
如果你再次运行命令,应该能看到下图:

大功告成!
webview.html应该是一个完整的HTML文档。使用HTML片段或者格式错乱的HTML会造成异常。
更新webview内容
webview.html也能在webview创建后更新内容,让我们用猫猫轮播图使Cat Coding具有动态性:
import * as vscode from 'vscode';const cats = {'Coding Cat': 'https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif','Compiling Cat':'https://media.giphy.com/media/mlvseq9yvZhba/giphy.gif'};export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, { });let iteration = 0;const updateWebview = () => {const cat = iteration++ % 2 ? 'Compiling Cat' : 'Coding Cat'panel.title = cat;panel.webview.html = getWebviewContent(cat);}// 设置初始化内容updateWebview();// 每秒更新内容setInterval(updateWebview, 1000);}));}function getWebviewContent(cat: keyof typeof cats) {return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cat Coding</title></head><body><img src="${cats[cat]}" width="300" /></body></html>`;}

因为webview.html方法替换了整个webview内容,页面看起来像重新加载了一个iframe。记住:如果你在webview中使用了脚本,那就意味着webview.html的重置会使脚本状态重置。
上述示例也使用了webview.title改变编辑器中的展示的文件名称,设置标题不会使webview重载。
生命周期
webview从属于创建他们的插件,插件必须保持住从webview返回的createWebviewPanel。如果你的插件失去了这个关联,它就不能再访问webview了,不过即使这样,webview还会继续展示在VS Code中。
因为webview是一个文本编辑器视图,所以用户可以随时关闭webview。当用户关闭了webview面板后,webview就被销毁了。在我们的例子中,销毁webview时抛出了一个异常,说明我们上面的示例中使用的seInterval实际上产生了非常严重的Bug:如果用户关闭了面板,setInterval会继续触发,而且还会尝试更新panel.webview.html,这当然会抛出异常。喵星人可不喜欢异常,我们现在就来解决这个问题吧。
onDidDispose事件在webview被销毁时触发,我们在这个事件结束之后更新并释放webview资源。
import * as vscode from 'vscode';const cats = {'Coding Cat': 'https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif','Compiling Cat':'https://media.giphy.com/media/mlvseq9yvZhba/giphy.gif'};export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, {});let iteration = 0;const updateWebview = () => {const cat = iteration++ % 2 ? 'Compiling Cat' : 'Coding Cat'panel.title = cat;panel.webview.html = getWebviewContent(cat);}updateWebview();const interval = setInterval(updateWebview, 1000);panel.onDidDispose(() => {// 当面板关闭时,取消webview内容之后的更新clearInterval(interval);}, null, context.subscriptions)}));}
插件也可以通过编程方式关闭webview视图——调用它们的dispose()方法。我们假设,现在限制我们的猫猫每天工作5秒钟:
export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, {});panel.webview.html = getWebviewContent('https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif');// 5秒后,程序性地关闭webview面板const timeout = setTimeout(() => panel.dispose(), 5000)panel.onDidDispose(() => {// 在第五秒结束之前处理用户手动的关闭动作clearTimeout(timeout);}, null, context.subscriptions)}));}
移动和可见性
当webview面板被移动到了非激活标签上,它就隐藏起来了。但这时并不是销毁,当重新激活标签后,VS Code会从webview.html自动恢复webview的内容。

.visible属性告诉你当前webview面板是否是可见的。
插件也可以通过调用reveal()方法,程序性地将webview面板激活。这个方法可以接受一个用于放置面板的目标视图布局。一个面板一次只能显示在一个编辑布局中。调用reveal()或者拖动webview面板到新的编辑布局中去。

现在更新我们的插件,一次只允许存在一个webview视图。如果面板处于非激活状态,那catCoding.start命令就把这个面板激活。
export function activate(context: vscode.ExtensionContext) {// 追踪当前webview面板let currentPanel: vscode.WebviewPanel | undefined = undefined;context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const columnToShowIn = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;if (currentPanel) {// 如果我们已经有了一个面板,那就把它显示到目标列布局中currentPanel.reveal(columnToShowIn);} else {// 不然,创建一个新面板currentPanel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", columnToShowIn, {});currentPanel.webview.html = getWebviewContent('https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif');// 当前面板被关闭后重置currentPanel.onDidDispose(() => {currentPanel = undefined;}, null, context.subscriptions);}}));}

不论何时,如果webview的可见性改变了,或者当webview移动到了新的视图布局中,就会触发onDidChangeViewState。我们的插件可以利用这个时间改变布局中的webview显示的猫:
const cats = {'Coding Cat': 'https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif','Compiling Cat':'https://media.giphy.com/media/mlvseq9yvZhba/giphy.gif','Testing Cat': 'https://media.giphy.com/media/3oriO0OEd9QIDdllqo/giphy.gif'};export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, {});panel.webview.html = getWebviewContent(cats['Coding Cat']);// 根据视图状态变动更新内容panel.onDidChangeViewState(e => {const panel = e.webviewPanel;switch (panel.viewColumn) {case vscode.ViewColumn.One:updateWebviewForCat(panel, 'Coding Cat');return;case vscode.ViewColumn.Two:updateWebviewForCat(panel, 'Compiling Cat');return;case vscode.ViewColumn.Three:updateWebviewForCat(panel, 'Testing Cat');return;}}, null, context.subscriptions);}));}function updateWebviewForCat(panel: vscode.WebviewPanel, catName: keyof typeof cats) {panel.title = catName;panel.webview.html = getWebviewContent(cats[catName]);}

检查和调试webviews
在命令面板中输入Developer: Toggle Developer Tools能帮助你调试webview。运行命令之后会为当前可见的webview加载一个devtool:

webview的内容是在webview文档中的一个iframe中的,用开发者工具检查和修改webview的DOM,在webview内调试脚本。
如果你用了webview开发者工具的console,确保你在Console面板左上角的下拉框里选中了当前激活窗体环境:

激活窗体环境是webview脚本执行的地方,另外,Developer: Reload Webview命令会刷新所有已激活的webview。如果你需要重置一个webview的状态,这个命令会非常有用,或者你想要读取硬盘内容的webview更新一下,也可以使用这个方法。
加载本地内容
webview运行在独立的环境中,因此不能直接访问本地资源,这是出于安全性考虑的做法。这也意味着要想从你的插件中加载图片、样式等其他资源,或是从用户当前的工作区加载任何内容的话,你必须使用webview中的vscode-resource:协议。
vscode-resource:协议就像file:协议一样,不过它只允许访问本地文件。和file:一样的是,vscode-resource:只能从绝对路径中加载资源。
想想一下,我们想要从本地把喵喵们的gif打包进来,而不是从Giphy(国外出名的gif收集站)里加载进来。要想做到这点,我们首先给本地文件新建一个URI,然后用vscode-resource:协议更新这些URI:
import * as vscode from 'vscode';import * as path from 'path';export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, { });// 获取磁盘上的资源路径const onDiskPath = vscode.Uri.file(path.join(extensionPath, 'media', 'cat.gif'));// 获取在webview中使用的特殊URIconst catGifSrc = onDiskPath.with({ scheme: 'vscode-resource' });panel.webview.html = getWebviewContent(catGifSrc);}));}
catGifSrc的值最后会像这样
vscode-resource:/Users/toonces/projects/vscode-cat-coding/media/cat.gif
默认情况下,scode-resource:只能访问下列地址的资源:
- 你的插件安装的目录
- 用户当前激活的工作区
你也可以用data URI将资源直接嵌套到webview中去。
控制本地资源访问
使用localResourceRoots选项,webview可以控制vscode-resource:加载的的资源。
localResourceRoots定义了可能被加载的本地内容的根URI。
我们用localResourceRoots去约束Cat Codingwebview只加载我们插件的media目录下的内容:
import * as vscode from 'vscode';import * as path from 'path';export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, {// Only allow the webview to access resources in our extension's media directorylocalResourceRoots: [vscode.Uri.file(path.join(extensionPath, 'media'))]});const onDiskPath = vscode.Uri.file(path.join(extensionPath, 'media', 'cat.gif'));const catGifSrc = onDiskPath.with({ scheme: 'vscode-resource' });panel.webview.html = getWebviewContent(catGifSrc);}));}
为了禁止所有的本地资源,只要把localResourceRoots设为[]就好了。
通常来说,webview应该和加载本地资源一样严格,然而,vscode-resource和localResourceRoots并不保证百分百的安全性。请确保你的webview遵循安全性最佳实践,强烈建议考虑添加一个内容安全政策以便约束之后加载的内容。
给webview内容加上主题
webview可以基于当前的VS Code主题和CSS改变自身的样式。VS Code将主题分成3中类别,而且在body元素上加上了特殊类名以表明当前主题:
vscode-light——亮色主题vscode-dark——暗色主题vscode-high-contrast——高反差主题
下列CSS改变了基于用户当前主题的webview字体颜色:
body.vscode-light {color: black;}body.vscode-dark {color: white;}body.vscode-high-contrast {color: red;}
当开发一个webview应用的时候,请保证应用能在三种主题下都可以运作,务必在高反差模式下测试你的webview,以便有视觉障碍的用户也能正常使用。
webview可以通过CSS variables访问VS Code主题,这些变量以vscode为前缀,并且用-替代了.,例如editor.foreground变成了var(--vscode-editor-foreground):
code {color: var(--vscode-editor-foreground);}
脚本和信息传递
既然webview就像iframe一样,也就是说它们也可以运行脚本,webview中的Javascript默认是禁用的,不过我们能用enableScripts: true打开它。
让我们写一段脚本,追踪我们家喵星人写代码的行数。运行一个基础脚本非常的容易,但是注意这个示例只作演示用途,在实践中,你的webview应该遵循内容安全政策,禁止行内脚本。
import * as path from 'path';import * as vscode from 'vscode';export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, {// 在webview中启用脚本enableScripts: true});panel.webview.html = getWebviewContent();}));}function getWebviewContent() {return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cat Coding</title></head><body><img src="https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif" width="300" /><h1 id="lines-of-code-counter">0</h1><script>const counter = document.getElementById('lines-of-code-counter');let count = 0;setInterval(() => {counter.textContent = count++;}, 100);</script></body></html>`;}

哇!真是位高产的喵主子!
!> webveiw的脚本能做到任何普通网页脚本能做到的事情,但是webview运行在自己的上下文中,脚本不能访问VS Code API。
将插件的信息传递到webview
插件可以用webview.postMessage()将数据发送到它的webview中。这个方法能发送任何序列化的JSON数据到webview中,在webview中则通过message事件接受信息。
我们现在就来看看这个实现,在Cat Coding中新增一个命令来表示我们家的喵在重构他的代码(所以会减少代码总行数)。新增catCoding.doRefactor命令,利用postMessage把指示发送到webview中,webview中的window.addEventListener('message' event => { ... })则会处理这些信息:
export function activate(context: vscode.ExtensionContext) {// 现在只有一只喵喵程序员了let currentPanel: vscode.WebviewPanel | undefined = undefined;context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {if (currentPanel) {currentPanel.reveal(vscode.ViewColumn.One);} else {currentPanel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, {enableScripts: true});currentPanel.webview.html = getWebviewContent();currentPanel.onDidDispose(() => { currentPanel = undefined; }, undefined, context.subscriptions);}}));// 我们新的命令context.subscriptions.push(vscode.commands.registerCommand('catCoding.doRefactor', () => {if (!currentPanel) {return;}// 把信息发送到webview// 你可以发送任何序列化的JSON数据currentPanel.webview.postMessage({ command: 'refactor' });}));}function getWebviewContent() {return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cat Coding</title></head><body><img src="https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif" width="300" /><h1 id="lines-of-code-counter">0</h1><script>const counter = document.getElementById('lines-of-code-counter');let count = 0;setInterval(() => {counter.textContent = count++;}, 100);// Handle the message inside the webviewwindow.addEventListener('message', event => {const message = event.data; // The JSON data our extension sentswitch (message.command) {case 'refactor':count = Math.ceil(count * 0.5);counter.textContent = count;break;}});</script></body></html>`;
将webview的信息传递到插件中
webview也可以把信息传递回对应的插件中,用VS Code API 为webview提供的postMessage函数我们就可以完成这个目标。调用webview中的acquireVsCodeApi获取VS Code API对象。这个函数在一个会话中只能调用一次,你必须保持住这个方法返回的VS Code API实例,然后再转交到需要调用这个实例的地方。
现在我们在Cat Coding添加一个弹出bug的警示框:
export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, {enableScripts: true});panel.webview.html = getWebviewContent();// 处理webview中的信息panel.webview.onDidReceiveMessage(message => {switch (message.command) {case 'alert':vscode.window.showErrorMessage(message.text);return;}}, undefined, context.subscriptions);}));}function getWebviewContent() {return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cat Coding</title></head><body><img src="https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif" width="300" /><h1 id="lines-of-code-counter">0</h1><script>(function() {const vscode = acquireVsCodeApi();const counter = document.getElementById('lines-of-code-counter');let count = 0;setInterval(() => {counter.textContent = count++;// Alert the extension when our cat introduces a bugif (Math.random() < 0.001 * count) {vscode.postMessage({command: 'alert',text: '🐛 on line ' + count})}}, 100);}())</script></body></html>`;}

出于安全性考虑,你必须保证VS Code API的私有性,也不会泄露到全局状态中去。
安全性
每一个你创建的webview都必须遵循这些基础的安全性最佳实践。
限制能力
webview应该留有它所需的最小功能集合即可。例如:如果你的webview不需要运行脚本,就不要设置enableScripts: true。如果你的webview不需要加载用户工作区的资源,就把localResourceRoots设置为[vscode.Uri.file(extensionContext.extensionPath)]或者[]以便禁止访问任何本地资源。
内容安全策略
内容安全策略可以进一步限制webview可以加载和执行的内容。例如:内容安全策略强制可以运行在webview中的脚本白名单,或者告诉webview只加载带https协议的图片。
要想加上内容安全策略,将<meta http-equiv="Content-Security-Policy">指令放到webview的<head>中
function getWebviewContent() {return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none';"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cat Coding</title></head><body>...</body></html>`;}
default-src 'none';策略直接禁止了所有内容。我们可以按插件需要的最少内容修改这个指令,如只允许通过https加载本地脚本、样式和图片:
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src vscode-resource:; style-src vscode-resource:;">
上述策略也隐式地禁用了内联脚本和样式。把内联样式和脚本提取到外部文件中是一个非常好的实践,也不会与内容安全策略冲突。
只通过https加载内容
如果你的webview允许加载外部资源,我们强烈建议你只允许通过https加载而不要使用http,上面的例子已经用内容安全策略展示了使用https的方式。
审查用户输入
就像构建普通HTML页面一样,你也同样需要在webview中审查用户输入的内容。 没有审查输入内容可能会导致内容注入,也就意味着将用户置于了危险之中。
可能需要审查的值:
- 文件内容
- 文件和文件夹路径
- 用户工作区设置
可以考虑用一个辅助库去构建HTML模板,或者确保所有来自用户工作区的内容都通过了审查
只依赖审查内容的安全性是不够的,你也要遵循其他安全性的最佳实践,尽可能减少潜在的内容注入。
持久性
在webview的标准生命周期中,createWebviewPanel负责创建和销毁(用户关闭或者调用.dispose()方法)webview。而webview的内容再是在webview可见时创建的,在webview处于非激活状态时销毁。webview处于非激活标签中时,任何webview中的保留的状态都会丢失。
所以最好减少webview中的状态,取而代之用消息传递储存状态。
getState和setState
运行在webview中的脚本可以使用getState和setState方法保存和恢复JSON序列化的状态对象。这个状态可以一直保留,即使webview面板已经被隐藏,只有当它销毁时,状态则会一起销毁。
// webview中的脚本const vscode = acquireVsCodeApi();const counter = document.getElementById('lines-of-code-counter');// 检查是否需要恢复状态const previousState = vscode.getState();let count = previousState ? previousState.count : 0;counter.textContent = count;setInterval(() => {counter.textContent = count++;// 更新已经保存的状态vscode.setState({ count })}, 100);
getState和setState是用来保存状态的比较好的办法,因为他们的性能消耗要远低于retainContextWhenHidden。
序列化
使用WebviewPanelSerializer之后,你的webview可以在VS Code关闭后自动恢复。序列化构建于getState和setState之上,只有你的插件注册了WebviewPanelSerializer,这个功能才会生效。
给插件的package.json添加一个onWebviewPanel激活事件,然后我们的代码喵就能在VS Code重启后继续工作了:
"activationEvents": [...,"onWebviewPanel:catCoding"]
这个激活事件确保我们的插件不论VS Code何时恢复catCodingwebview时都会启动。
然后在我们插件的activate方法中调用registerWebviewPanelSerializer注册一个新的WebviewPanelSerializer,这个函数负责恢复webview之前保存的内容。其中的state就是webview用setState设置的JSON格式的状态。
export function activate(context: vscode.ExtensionContext) {// 常见设置...// 确保我们注册了一个序列化器vscode.window.registerWebviewPanelSerializer('catCoding', new CatCodingSerializer());}class CatCodingSerializer implements vscode.WebviewPanelSerializer {async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) {// `state`是webview内调用`setState`保留住的console.log(`Got state: ${state}`);// 恢复我们的webview内容//// 确保我们将`webviewPanel`传递到了这里// 然后用事件侦听器恢复我们的内容webviewPanel.webview.html = getWebviewContent();}}
在VS Code中打开一个喵喵打代码的面板,关闭后重启就能看到这个面板恢复到了之前的状态和位置。
隐藏时保留上下文
如果webview的视图非常复杂,或者状态不能很快地保存和恢复,你则可以用retainContextWhenHidden选项,这个选项在不可见的状态中保存了webview的内容,即使webview本身不处于激活状态。
虽然Cat Coding说不上有很复杂的状态,不过我们可以打开retainContextWhenHidden看看webview的行为会发生什么变化:
import * as vscode from 'vscode';export function activate(context: vscode.ExtensionContext) {context.subscriptions.push(vscode.commands.registerCommand('catCoding.start', () => {const panel = vscode.window.createWebviewPanel('catCoding', "Cat Coding", vscode.ViewColumn.One, {enableScripts: true,retainContextWhenHidden: true});panel.webview.html = getWebviewContent();}));}function getWebviewContent() {return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Cat Coding</title></head><body><img src="https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif" width="300" /><h1 id="lines-of-code-counter">0</h1><script>const counter = document.getElementById('lines-of-code-counter');let count = 0;setInterval(() => {counter.textContent = count++;}, 100);</script></body></html>`;}

我们可以注意到计数器没有重置,webview隐藏之后就恢复了。而且不需要多余的代码!retainContextWhenHidden的行为就像浏览器一样,脚本和其他内容被暂时挂起,但是一旦webview可见之后就会立即恢复。但是在webview隐藏状态下,你还是不能给它发送消息的。
虽然retainContextWhenHidden很吸引人,但是记住这个功能的内容占用很高,只有其他的持久化技术无能为力之时再选择这种方式。
下一步
如果你想了解学习更多VS Code扩展性的内容,请查看下列主题:
- 扩展Visual Studio Code - 其他扩展VS Code的方式
- 其他插件示例 - 我们的插件项目示例列表
