支持远程开发和 GitHub Codespaces
Visual Studio Code 远程开发允许您透明地与其他计算机(无论是虚拟机还是物理机)上的源代码和运行时环境进行交互。GitHub Codespaces是一项通过可从 VS Code 和基于浏览器的编辑器访问的托管云托管环境扩展这些功能的服务。
为了确保性能,远程开发和 GitHub Codespaces 都透明地远程运行某些 VS Code 扩展。然而,这可能会对扩展的工作方式产生微妙的影响。虽然许多扩展无需任何修改即可工作,但您可能需要进行更改,以便您的扩展在所有环境中正常工作,尽管这些更改通常相当小。
本文总结了扩展作者需要了解的有关远程开发和 Codespaces 的信息,包括扩展架构、如何在远程工作区或 Codespace 中调试扩展,以及扩展无法正常工作时应采取的措施的建议。
架构和扩展类型
为了使远程开发或代码空间的使用对用户尽可能透明,VS Code 区分了两种扩展:
-
UI 扩展:这些扩展有助于 VS Code 用户界面,并且始终在用户的本地计算机上运行。UI 扩展无法直接访问远程工作区中的文件,也无法运行该工作区或计算机上安装的脚本/工具。示例 UI 扩展包括:主题、片段、语言语法和键盘映射。
-
工作区扩展:这些扩展在工作区所在的同一台计算机上运行。在本地工作区中时,工作区扩展在本地计算机上运行。在远程工作区中或使用 Codespaces 时,工作区扩展在远程计算机/环境上运行。工作区扩展可以访问工作区中的文件,以提供丰富的多文件语言服务、调试器支持,或对工作区中的多个文件执行复杂的操作(直接或通过调用脚本/工具)。虽然工作区扩展不专注于修改 UI,但它们也可以提供资源管理器、视图和其他 UI 元素。
当用户安装扩展时,VS Code 会根据其类型自动将其安装到正确的位置。如果扩展可以以任一类型运行,VS Code 将尝试选择最适合该情况的扩展;UI 扩展将在 VS Code 的本地扩展主机中运行,而工作空间扩展将在位于小型VS Code 服务器中的远程扩展主机中运行(如果它存在于远程工作区中),否则将在 VS Code 的本地扩展主机(如果存在)中运行本地。为了确保最新的 VS Code 客户端功能可用,服务器需要与 VS Code 客户端版本完全匹配。因此,当您使用 Codespaces 在容器中、远程 SSH 主机上或 Windows Subsystem for Linux (WSL) 中打开文件夹时,远程开发或 GitHub Codespaces 扩展会自动安装(或更新)服务器。(VS Code 还会自动管理服务器的启动和停止,因此用户不会意识到它的存在。)
VS Code API 旨在从 UI 或工作区扩展调用时自动在正确的计算机(本地或远程)上运行。但是,如果您的扩展使用 VS Code 未提供的 API(例如使用 Node API 或运行 shell 脚本),则远程运行时可能无法正常工作。我们建议您测试扩展的所有功能是否在本地和远程工作区中正常工作。
调试扩展
虽然您可以在远程环境中安装扩展的开发版本进行测试,但如果遇到问题,您可能希望直接在远程环境中调试扩展。在本节中,我们将介绍如何在GitHub Codespaces、本地容器、SSH 主机或WSL中编辑、启动和调试您的扩展。
通常,测试的最佳起点是使用限制端口访问的远程环境(例如 Codespaces、容器或具有限制性防火墙的远程 SSH 主机),因为在这些环境中工作的扩展往往在限制较少的环境中工作,例如WSL。
使用 GitHub Codespaces 进行调试
在GitHub Codespaces预览中调试扩展可能是一个很好的起点,因为您可以使用 VS Code 和基于浏览器的 Codespaces 编辑器进行测试和故障排除。如果愿意,您还可以使用自定义 Dev Containers 。
按着这些次序:
-
导航到 GitHub 上包含您的扩展的存储库,然后在代码空间中将其打开,以便在基于浏览器的编辑器中使用它。如果您愿意,还可以在 VS Code 中打开代码空间。
-
虽然 GitHub Codespaces 的默认映像应具备大多数扩展所需的所有先决条件,但您可以在新的 VS Code 终端窗口 ( ⌃⇧`(Windows、Linux Ctrl+Shift+`)中安装任何其他所需的依赖项(例如,使用 或
yarn install
)))。sudo apt-get
-
最后,按F5或使用“运行和调试”视图在代码空间中启动扩展。
注意:您将无法在出现的窗口中打开扩展源代码文件夹,但可以打开子文件夹或代码空间中的其他位置。
出现的扩展开发主机窗口将包括在代码空间中运行的扩展,并附加了调试器。
在自定义 Dev Containers 中调试
按着这些次序:
-
要在本地使用 Dev Containers ,请安装并配置 Dev Containers 扩展,然后使用“文件”>“打开...”/“打开文件夹...”在 VS Code 中本地打开源代码。要使用 Codespaces,请导航到 GitHub 上包含您的扩展的存储库,然后在代码空间中打开它,以便在基于浏览器的编辑器中使用它。如果您愿意,还可以在 VS Code 中打开代码空间。
-
从命令面板 ( F1 )选择Dev Containers: Add Dev Container Configuration Files...或Codespaces: Add Dev Container Configuration Files...,然后选择Node.js 和 TypeScript(如果不使用 TypeScript,则选择 Node.js)添加所需的容器配置文件。
-
可选:运行此命令后,您可以修改文件夹的内容
.devcontainer
以包含其他构建或运行时要求。有关详细信息,请参阅深入的创建 Dev Containers 文档。 -
运行 Dev Containers :在容器或代码空间中重新打开:添加 Dev Containers 配置文件...,VS Code 将立即设置容器并连接。现在,您将能够从容器内部开发源代码,就像在本地情况下一样。
-
在新的 VS Code 终端窗口 ( ⌃⇧` (Windows、Linux Ctrl+Shift+` ) ) 中运行
yarn install
或 ,以确保安装 Linux 版本的 Node.js 本机依赖项。您还可以安装其他操作系统或运行时依赖项,但您可能还想添加这些依赖项,以便在重建容器时它们可用。npm install
.devcontainer/Dockerfile
-
最后,按F5或使用“运行和调试”视图在同一容器内启动扩展并附加调试器。
注意:您将无法在出现的窗口中打开扩展源代码文件夹,但可以打开子文件夹或容器中的其他位置。
出现的扩展开发主机窗口将包括在步骤 2 中定义的容器中运行的扩展,并附加了调试器。
使用 SSH 进行调试
请按照步骤:
-
安装并配置 Remote-SSH 扩展后,从 VS Code 的命令面板 ( F1 ) 中选择Remote-SSH: Connect to Host...以连接到主机。
-
连接后,使用“文件”>“打开...”/“打开文件夹...”来选择其中包含扩展源代码的远程文件夹,或者从命令面板 ( F1 ) 中选择“Git: Clone”以克隆它并在远程打开它主持人。
-
在新的 VS Code 终端窗口 ( ⌃⇧` (Windows、Linux Ctrl+Shift+` ) ) 中安装可能缺少的任何必需依赖项(例如使用
yarn install
或) 。apt-get
-
最后,按F5或使用“运行和调试”视图在远程主机上启动内部扩展并附加调试器。
注意:您将无法在出现的窗口中打开扩展源代码文件夹,但您可以打开子文件夹或 SSH 主机上的其他位置。
出现的扩展开发主机窗口将包括在 SSH 主机上运行的扩展以及附加的调试器。
使用 WSL 进行调试
按着这些次序:
-
安装并配置 WSL 扩展后,从VS Code 的命令面板 ( F1 ) 中选择WSL:新窗口。
-
在出现的新窗口中,使用“文件”>“打开.../打开文件夹...”选择其中包含扩展源代码的远程文件夹,或者从命令面板 ( F1 ) 中选择“Git: Clone”以克隆并打开它在 WSL 中。
提示:您可以选择
/mnt/c
文件夹来访问 Windows 端的任何克隆源代码。 -
apt-get
在新的 VS Code 终端窗口 ( ⌃⇧` (Windows、Linux Ctrl+Shift+` ) ) 中安装可能缺少的任何必需依赖项(例如使用) 。您至少需要运行yarn install
或npm install
确保 Linux 版本的本机 Node.js 依赖项可用。 -
最后,按F5或使用“运行和调试”视图启动扩展并附加调试器,就像在本地一样。
注意:您将无法在出现的窗口中打开扩展源代码文件夹,但可以在 WSL 中打开子文件夹或其他位置。
出现的扩展开发主机窗口将包括在 WSL 中运行的扩展以及附加的调试器。
安装扩展的开发版本
每当 VS Code 自动在 SSH 主机、容器或 WSL 内或通过 GitHub Codespaces 安装扩展时,都会使用 Marketplace 版本(而不是本地计算机上已安装的版本)。
虽然这在大多数情况下是有意义的,但您可能希望使用(或共享)扩展的未发布版本进行测试,而无需设置调试环境。要安装扩展的未发布版本,您可以将扩展打包为VSIX
并将其手动安装到已连接到正在运行的远程环境的 VS Code 窗口中。
按着这些次序:
- 如果这是已发布的扩展,您可能需要添加
"extensions.autoUpdate": false
以settings.json
防止其自动更新到最新的 Marketplace 版本。 - 接下来,用于
vsce package
将扩展打包为 VSIX。 - 连接到代码空间、 Dev Containers 、SSH 主机或WSL 环境。
- 使用“扩展”视图“更多操作” ( ) 菜单中提供的“从 VSIX 安装...”命令在此特定窗口(不是本地窗口)中安装扩展。
...
- 出现提示时重新加载。
提示:安装后,您可以使用Developer: Show Running Extensions命令查看 VS Code 是在本地还是远程运行扩展。
使用远程扩展处理依赖关系
扩展可以依赖于 API 的其他扩展。例如:
- 扩展程序可以从其函数中导出 API
activate
。 - 此 API 将可供在同一扩展主机中运行的所有扩展使用。
- 消费者扩展在其声明中声明
package.json
它们依赖于使用该属性的提供扩展extensionDependencies
。
当所有扩展都在本地运行并共享同一扩展主机时,扩展依赖关系可以正常工作。
在处理远程场景时,远程运行的扩展可能对本地运行的扩展具有扩展依赖性。例如,本地扩展公开对远程扩展的功能至关重要的命令。在这种情况下,我们建议远程扩展将本地扩展声明为extensionDependency
,但问题是这些扩展运行在两个不同的扩展主机上,这意味着来自提供者的 API 无法提供给使用者。因此,要求提供扩展完全放弃通过"api": "none"
在其扩展的package.json
. 扩展仍然可以使用 VS Code 命令(异步)进行通信。
这似乎对提供的扩展有不必要的严格限制,但仅使用的扩展"api": "none"
放弃了从其activate
方法返回 API 的能力。在其他扩展主机上执行的使用者扩展仍然可以依赖它们并将被激活。
常见问题
VS Code 的 API 旨在自动在正确的位置运行,无论您的扩展位于何处。考虑到这一点,有一些 API 可以帮助您避免意外行为。
执行位置不正确
如果您的扩展未按预期运行,它可能运行在错误的位置。最常见的是,当您希望它仅在本地运行时,它会显示为远程运行的扩展。您可以使用命令面板 ( F1 ) 中的开发人员:显示正在运行的扩展命令来查看扩展的运行位置。
如果开发人员:显示正在运行的扩展命令显示 UI 扩展被错误地视为工作区扩展,反之亦然,请尝试按照扩展种类部分中所述在extensionKind
扩展的package.json中设置属性。
您可以通过设置快速测试更改扩展类型的效果。此设置是扩展 ID 到扩展类型的映射。例如,如果要强制Azure 数据库扩展成为 UI 扩展(而不是其工作区默认设置),并将远程 - SSH:编辑配置文件扩展设置为工作区扩展(而不是其 UI 默认设置),则可以设置:remote.extensionKind
{
"remote.extensionKind": {
"ms-azuretools.vscode-cosmosdb": ["ui"],
"ms-vscode-remote.remote-ssh-edit": ["workspace"]
}
}
使用remote.extensionKind
允许您快速测试扩展的发布版本,而无需修改package.json
和重建它们。
持久化扩展数据或状态
在某些情况下,您的扩展可能需要保留不属于settings.json
或单独的工作区配置文件(例如.eslintrc
)的状态信息。为了解决此问题,VS Code 在激活期间传递给扩展的对象上提供了一组有用的存储属性vscode.ExtensionContext
。如果您的扩展已经利用了这些属性,那么无论在何处运行,它都应该继续运行。
但是,如果您的扩展依赖于当前的 VS Code 路径约定(例如~/.vscode
)或某些操作系统文件夹(例如~/.config/Code
在 Linux 上)的存在来保存数据,则可能会遇到问题。幸运的是,更新扩展并避免这些挑战应该很简单。
如果要保留简单的键值对,则可以分别使用vscode.ExtensionContext.workspaceState
或存储工作区特定或全局状态信息vscode.ExtensionContext.globalState
。如果您的数据比键值对更复杂,则 globalStorageUri
和storageUri
属性提供“安全”URI,您可以使用它们在文件中读取/写入全局工作区特定信息。
要使用 API:
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('myAmazingExtension.persistWorkspaceData', async () => {
if (!context.storageUri) {
return;
}
// Create the extension's workspace storage folder if it doesn't already exist
try {
// When folder doesn't exist, and error gets thrown
await vscode.workspace.fs.stat(context.storageUri);
} catch {
// Create the extension's workspace storage folder
await vscode.workspace.fs.createDirectory(context.storageUri)
}
const workspaceData = vscode.Uri.joinPath(context.storageUri, 'workspace-data.json');
const writeData = new TextEncoder().encode(JSON.stringify({ now: Date.now() }));
vscode.workspace.fs.writeFile(workspaceData, writeData);
}
));
context.subscriptions.push(
vscode.commands.registerCommand('myAmazingExtension.persistGlobalData', async () => {
if (!context.globalStorageUri) {
return;
}
// Create the extension's global (cross-workspace) folder if it doesn't already exist
try {
// When folder doesn't exist, and error gets thrown
await vscode.workspace.fs.stat(context.globalStorageUri);
} catch {
await vscode.workspace.fs.createDirectory(context.globalStorageUri)
}
const workspaceData = vscode.Uri.joinPath(context.globalStorageUri, 'global-data.json');
const writeData = new TextEncoder().encode(JSON.stringify({ now: Date.now() }));
vscode.workspace.fs.writeFile(workspaceData, writeData);
));
}
在机器之间同步用户全局状态
如果您的扩展需要在不同的计算机上保留某些用户状态,则使用将该状态提供给Settings Syncvscode.ExtensionContext.globalState.setKeysForSync
。这有助于防止在多台计算机上向用户显示相同的欢迎或更新页面。
扩展功能setKeysforSync
主题中有一个使用示例。
持久的秘密
如果您的扩展需要保留密码或其他机密,您可能需要使用 Visual Studio Code 的SecretStorage API,它提供了一种在加密支持的文件系统上安全存储文本的方法。例如,在桌面上,我们使用 Electron 的safeStorage API来加密机密,然后再将其存储在文件系统上。API 将始终将机密存储在客户端,但无论您的扩展程序在何处运行,您都可以使用此 API 并检索相同的机密值。
注意:此 API 是保存密码和机密的推荐方法。您不应使用
vscode.ExtensionContext.workspaceState
或 来存储您的机密vscode.ExtensionContext.globalState
,因为这些 API 以明文形式存储数据。
这是一个例子:
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
// ...
const myApiKey = context.secrets.get('apiKey');
// ...
context.secrets.delete('apiKey');
// ...
context.secrets.store('apiKey', myApiKey);
}
使用剪贴板
从历史上看,扩展作者曾使用 Node.js 模块来clipboardy
与剪贴板进行交互。不幸的是,如果您在工作区扩展中使用这些模块,它们将使用远程剪贴板而不是用户的本地剪贴板。VS Code 剪贴板 API 解决了这个问题。无论调用它的扩展类型如何,它始终在本地运行。
要在扩展中使用 VS Code 剪贴板 API:
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('myAmazingExtension.clipboardIt', async () => {
// Read from clipboard
const text = await vscode.env.clipboard.readText();
// Write to clipboard
await vscode.env.clipboard.writeText(
`It looks like you're copying "${text}". Would you like help?`
);
})
);
}
在本地浏览器或应用程序中打开某些内容
生成进程或使用模块(例如opn
针对特定 URI 启动浏览器或其他应用程序)可以很好地适应本地场景,但工作区扩展远程运行,这可能会导致应用程序在错误的一侧启动。VS Code 远程开发部分地填充了opn
节点模块,以允许现有扩展发挥作用。您可以使用 URI 调用该模块,VS Code 将导致该 URI 的默认应用程序出现在客户端。但是,这不是完整的实现,因为不支持选项并且child_process
不返回对象。
我们建议扩展程序利用该vscode.env.openExternal
方法在本地操作系统上针对给定 URI 启动默认注册的应用程序,而不是依赖第三方节点模块。更好的是,vscode.env.openExternal
自动本地主机端口转发!您可以使用它指向远程计算机或代码空间上的本地 Web 服务器并提供内容,即使该端口被外部阻止也是如此。
注意:目前Codespaces基于浏览器的编辑器中的转发机制仅支持http和https请求。但是,从 VS Code 连接到代码空间时,您可以与任何 TCP 连接进行交互。
使用vscode.env.openExternal
API:
import * as vscode from 'vscode';
export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('myAmazingExtension.openExternal', () => {
// Example 1 - Open the VS Code homepage in the default browser.
vscode.env.openExternal(vscode.Uri.parse('https://vscode.github.net.cn'));
// Example 2 - Open an auto-forwarded localhost HTTP server.
vscode.env.openExternal(vscode.Uri.parse('http://localhost:3000'));
// Example 3 - Open the default email application.
vscode.env.openExternal(vscode.Uri.parse('mailto:<fill in your email here>'));
})
);
}
转发本地主机
虽然本地主机转发机制vscode.env.openExternal
很有用,但也可能存在您想要转发某些内容而不实际启动新的浏览器窗口或应用程序的情况。这就是vscode.env.asExternalUri
API 发挥作用的地方。
注意:目前Codespaces基于浏览器的编辑器中的转发机制仅支持http和https请求。但是,从 VS Code 连接到代码空间时,您可以与任何 TCP 连接进行交互。
使用vscode.env.asExternalUri
API:
import * as vscode from 'vscode';
import { getExpressServerPort } from './server';
export async function activate(context: vscode.ExtensionContext) {
const dynamicServerPort = await getWebServerPort();
context.subscriptions.push(vscode.commands.registerCommand('myAmazingExtension.forwardLocalhost', async () =>
// Make the port available locally and get the full URI
const fullUri = await vscode.env.asExternalUri(
vscode.Uri.parse(`http://localhost:${dynamicServerPort}`));
// ... do something with the fullUri ...
}));
}
需要注意的是,API 传回的 URI可能根本不引用 localhost,因此您应该完整地使用它。这对于无法使用 localhost 的基于浏览器的 Codespaces 编辑器尤其重要。
回调和 URI 处理程序
该vscode.window.registerUriHandler
API 允许您的扩展程序注册自定义 URI,如果在浏览器中打开该 URI,将在您的扩展程序中触发回调函数。注册 URI 处理程序的常见用例是使用OAuth 2.0身份验证提供程序(例如 Azure AD)实现服务登录。但是,它可用于您希望外部应用程序或浏览器向您的扩展程序发送信息的任何场景。
VS Code 中的远程开发和 Codespaces 扩展将透明地处理将 URI 传递给您的扩展,无论它实际运行在何处(本地或远程)。但是,vscode://
URI 无法与基于浏览器的 Codespaces 编辑器配合使用,因为在浏览器之类的设备中打开这些 URI 会尝试将它们传递到本地 VS Code 客户端,而不是基于浏览器的编辑器。幸运的是,这个问题可以通过使用vscode.env.asExternalUri
API 轻松解决。
vscode.window.registerUriHandler
让我们使用和的组合vscode.env.asExternalUri
来连接一个示例 OAuth 身份验证回调:
import * as vscode from 'vscode';
// This is ${publisher}.${name} from package.json
const extensionId = 'my.amazing-extension';
export async function activate(context: vscode.ExtensionContext) {
// Register a URI handler for the authentication callback
vscode.window.registerUriHandler({
handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
// Add your code for what to do when the authentication completes here.
if (uri.path === '/auth-complete') {
vscode.window.showInformationMessage('Sign in successful!');
}
}
});
// Register a sign in command
context.subscriptions.push(
vscode.commands.registerCommand(`${extensionId}.signin`, async () => {
// Get an externally addressable callback URI for the handler that the authentication provider can use
const callbackUri = await vscode.env.asExternalUri(
vscode.Uri.parse(`${vscode.env.uriScheme}://${extensionId}/auth-complete`)
);
// Add your code to integrate with an authentication provider here - we'll fake it.
vscode.env.clipboard.writeText(callbackUri.toString());
await vscode.window.showInformationMessage(
'Open the URI copied to the clipboard in a browser window to authorize.'
);
})
);
}
在 VS Code 中运行此示例时,它会连接一个可用作身份验证提供程序回调的 URI vscode://
。vscode-insiders://
当在基于浏览器的 Codespaces 编辑器中运行时,它会连接https://*.github.dev
URI,无需任何代码更改或特殊条件。
虽然 OAuth 超出了本文档的范围,但请注意,如果您将此示例改编为真正的身份验证提供程序,则可能需要在提供程序前面构建代理服务。这是因为并非所有提供商都允许vscode://
回调 URI,而其他提供商则不允许通过 HTTPS 进行回调的通配符主机名。我们还建议尽可能使用带有 PKCE 流的 OAuth 2.0 授权代码(例如,Azure AD 支持 PKCE),以提高回调的安全性。
远程运行或在 Codespaces 浏览器编辑器中运行时的不同行为
在某些情况下,您的工作区扩展可能需要改变远程运行时的行为。在其他情况下,您可能希望在 Codespaces 基于浏览器的编辑器中运行时改变其行为。VS Code 提供了三个 API 来检测这些情况:vscode.env.uiKind
、extension.extensionKind
和vscode.env.remoteName
。
接下来,您可以使用这三个API,如下所示:
import * as vscode from 'vscode';
export async function activate(context: vscode.ExtensionContext) {
// extensionKind returns ExtensionKind.UI when running locally, so use this to detect remote
const extension = vscode.extensions.getExtension('your.extensionId');
if (extension.extensionKind === vscode.ExtensionKind.Workspace) {
vscode.window.showInformationMessage('I am running remotely!');
}
// Codespaces browser-based editor will return UIKind.Web for uiKind
if (vscode.env.uiKind === vscode.UIKind.Web) {
vscode.window.showInformationMessage('I am running in the Codespaces browser editor!');
}
// VS Code will return undefined for remoteName if working with a local workspace
if (typeof vscode.env.remoteName === 'undefined') {
vscode.window.showInformationMessage('Not currently connected to a remote workspace.');
}
}
使用命令在扩展之间进行通信
某些扩展会返回 API 作为其激活的一部分,供其他扩展使用(通过vscode.extension.getExtension(extensionName).exports
)。虽然如果涉及的所有扩展都位于同一侧(所有 UI 扩展或所有工作区扩展),这些功能都可以工作,但这些功能在 UI 和工作区扩展之间不起作用。
幸运的是,VS Code 会自动将任何执行的命令路由到正确的扩展,无论其位置如何。您可以自由调用任何命令(包括其他扩展提供的命令),而不必担心影响。
如果您有一组需要相互交互的扩展,那么使用私有命令公开功能可以帮助您避免意外的影响。但是,作为参数传入的任何对象JSON.stringify
在传输之前都将被“字符串化”( ),因此该对象不能具有循环引用,并且在另一端最终将成为“普通旧 JavaScript 对象”。
例如:
import * as vscode from 'vscode';
export async function activate(context: vscode.ExtensionContext) {
// Register the private echo command
const echoCommand = vscode.commands.registerCommand(
'_private.command.called.echo',
(value: string) => {
return value;
}
);
context.subscriptions.push(echoCommand);
}
有关使用命令的详细信息,请参阅命令 API 指南。
使用网络视图 API
与剪贴板 API 一样,Webview API始终在用户的本地计算机或浏览器中运行,即使是从工作区扩展使用时也是如此。这意味着许多基于 Web 视图的扩展应该可以正常工作,即使在远程工作空间或代码空间中使用也是如此。但是,需要注意一些注意事项,以便您的 webview 扩展在远程运行时正常工作。
始终使用 asWebviewUri
您应该使用asWebviewUri
API 来管理扩展资源。需要使用此 API 而不是硬编码vscode-resource://
URI,以确保 Codespaces 基于浏览器的编辑器与您的扩展配合使用。有关详细信息,请参阅Webview API指南,但这里有一个简单的示例。
您可以在内容中使用 API,如下所示:
// Create the webview
const panel = vscode.window.createWebviewPanel(
'catWebview',
'Cat Webview',
vscode.ViewColumn.One
);
// Get the content Uri
const catGifUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'media', 'cat.gif')
);
// Reference it in your content
panel.webview.html = `<!DOCTYPE html>
<html>
<body>
<img src="${catGifUri}" width="300" />
</body>
</html>`;
使用动态 Web 视图内容的消息传递 API
VS Code webview 包含一个消息传递API,允许您动态更新 webview 内容,而无需使用本地 Web 服务器。即使您的扩展正在运行一些您想要与之交互以更新 webview 内容的本地 Web 服务,您也可以从扩展本身而不是直接从 HTML 内容执行此操作。
这是远程开发和 GitHub Codespaces 的一个重要模式,可确保您的 webview 代码在 VS Code 和 Codespaces 基于浏览器的编辑器中都能正常工作。
为什么使用消息传递而不是本地主机 Web 服务器?
iframe
另一种模式是在本地主机服务器中提供 Web 内容或让 WebView 内容直接与本地主机交互。不幸的是,默认情况下,localhost
Web 视图内部将解析为开发人员的本地计算机。这意味着对于远程运行的工作区扩展,它创建的 webview 将无法访问该扩展生成的本地服务器。即使您使用计算机的 IP,您连接的端口通常也会在云虚拟机或容器中默认被阻止。即使这在 VS Code 中有效,它在基于浏览器的 Codespaces 编辑器中也不起作用。
以下是使用远程 - SSH 扩展时出现的问题的说明,但 Dev Containers 和 GitHub Codespaces 也存在该问题:
如果可能,您应该避免这样做,因为它会使您的扩展变得非常复杂。消息传递API 可以实现相同类型的用户体验,而无需这些类型的麻烦。扩展本身将在远程端的 VS Code Server 中运行,因此它可以透明地与您的扩展因从 Web 视图传递给它的任何消息而启动的任何 Web 服务器进行交互。
从 Web 视图使用 localhost 的解决方法
如果由于某种原因无法使用消息传递API,有两个选项可与 VS Code 中的远程开发和 GitHub Codespaces 扩展配合使用。
每个选项都允许 webview 内容通过 VS Code 用于与 VS Code 服务器通信的同一通道进行路由。例如,如果我们更新上一节中远程 - SSH 的插图,您将看到以下内容:
选项 1 - 使用 asExternalUri
VS Code 1.40 引入了vscode.env.asExternalUri
API,允许扩展以编程方式转发本地http
和远程请求。https
当您的扩展在 VS Code 中运行时,您可以使用相同的 API 将请求localhost
从 Web 视图转发到 Web 服务器。
使用 API 获取 iframe 的完整 URI 并将其添加到您的 HTML 中。您还需要在 Web 视图中启用脚本并向 HTML 内容添加 CSP。
// Use asExternalUri to get the URI for the web server
const dynamicWebServerPort = await getWebServerPort();
const fullWebServerUri = await vscode.env.asExternalUri(
vscode.Uri.parse(`http://localhost:${dynamicWebServerPort}`)
);
// Create the webview
const panel = vscode.window.createWebviewPanel(
'asExternalUriWebview',
'asExternalUri Example',
vscode.ViewColumn.One,
{
enableScripts: true
}
);
const cspSource = panel.webview.cspSource;
panel.webview.html = `<!DOCTYPE html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; frame-src ${fullWebServerUri} ${cspSource} https:; img-src ${cspSource} https:; script-src ${cspSource}; style-src ${cspSource};"
/>
</head>
<body>
<!-- All content from the web server must be in an iframe -->
<iframe src="${fullWebServerUri}">
</body>
</html>`;
iframe
请注意,上例中提供的任何 HTML 内容都需要使用相对路径而不是硬编码localhost
。
选项 2 - 使用端口映射
如果您不打算支持 Codespaces 基于浏览器的编辑器,您可以使用portMapping
webview API 中提供的选项。(此方法也适用于 VS Code 客户端中的 Codespace,但不适用于浏览器)。
portMapping
要使用端口映射,请在创建 Web 视图时传入一个对象:
const LOCAL_STATIC_PORT = 3000;
const dynamicServerPort = await getWebServerPort();
// Create webview and pass portMapping in
const panel = vscode.window.createWebviewPanel(
'remoteMappingExample',
'Remote Mapping Example',
vscode.ViewColumn.One,
{
portMapping: [
// This maps localhost:3000 in the webview to the web server port on the remote host.
{ webviewPort: LOCAL_STATIC_PORT, extensionHostPort: dynamicServerPort }
]
}
);
// Reference the port in any full URIs you reference in your HTML.
panel.webview.html = `<!DOCTYPE html>
<body>
<!-- This will resolve to the dynamic server port on the remote machine -->
<img src="http://localhost:${LOCAL_STATIC_PORT}/canvas.png">
</body>
</html>`;
在此示例中,在远程和本地情况下,发出的任何请求都http://localhost:3000
将自动映射到 Express.js Web 服务器正在运行的动态端口。
使用原生 Node.js 模块
与 VS Code 扩展捆绑(或动态获取)的本机模块必须使用 Electron 的electron-rebuild
. 但是,VS Code Server 运行标准(非 Electron)版本的 Node.js,这可能会导致二进制文件在远程使用时失败。
为了解决这个问题:
- 包含(或动态获取)VS Code 附带的 Node.js 中“模块”版本的两组二进制文件(Electron 和标准 Node.js)。
- 检查是否
vscode.extensions.getExtension('your.extensionId').extensionKind === vscode.ExtensionKind.Workspace
根据扩展是远程运行还是本地运行来设置正确的二进制文件。 - 您可能还想按照类似的逻辑同时添加对非 x86_64 目标和 Alpine Linux 的支持。
您可以通过转到“帮助”>“开发人员工具process.versions.modules
”并在控制台中键入来找到 VS Code 使用的“模块”版本。但是,为了确保本机模块在不同的 Node.js 环境中无缝工作,您可能需要针对您想要支持的所有可能的 Node.js“模块”版本和平台(Electron Node.js、官方 Node.js Windows)编译本机模块/Darwin/Linux,所有版本)。node -tree-sitter模块就是一个很好的例子,它可以很好地做到这一点。
支持非 x86_64 主机或 Alpine Linux 容器
如果您的扩展纯粹是用 JavaScript/TypeScript 编写的,您可能不需要执行任何操作即可向musl
您的扩展添加对其他处理器架构或基于 Alpine Linux 的支持。
但是,如果您的扩展在 Debian 9+、Ubuntu 16.04+ 或 RHEL/CentOS 7+ 远程 SSH 主机、容器或 WSL 上运行,但在受支持的非 x86_64 主机(例如 ARMv7l)或 Alpine Linux 容器上失败,则该扩展可能包含 x86_64glibc
特定的本机代码或运行时,这些代码或运行时将在这些架构/操作系统上失败。
例如,您的扩展可能仅包含本机模块或运行时的 x86_64 编译版本。对于 Alpine Linux,由于Alpine Linux ( ) 和其他发行版 ( )的实现方式存在根本差异,所包含的本机代码或运行时可能无法工作。libc
musl
glibc
要解决此问题:
-
如果您要动态获取编译的代码,则可以通过使用检测非 x86_64 目标
process.arch
并下载为正确架构编译的版本来添加支持。如果您在扩展中包含所有受支持架构的二进制文件,则可以使用此逻辑来使用正确的架构。 -
对于 Alpine Linux,您可以使用检测操作系统
await fs.exists('/etc/alpine-release')
并再次下载或使用适用于musl
基础操作系统的正确二进制文件。 -
如果您不想支持这些平台,则可以使用相同的逻辑来提供良好的错误消息。
请务必注意,某些第三方 npm 模块包含可能导致此问题的本机代码。因此,在某些情况下,您可能需要与 npm 模块作者合作来添加其他编译目标。
避免使用 Electron 模块
虽然依赖扩展 API 未公开的内置 Electron 或 VS Code 模块可能很方便,但需要注意的是 VS Code Server 运行标准(非 Electron)版本的 Node.js。远程运行时这些模块将丢失。有一些例外,有特定的代码可以使它们工作。
使用基本 Node.js 模块或扩展 VSIX 中的模块可以避免这些问题。如果您绝对必须使用 Electron 模块,请确保在模块丢失时有后备方案。
下面的示例将使用 Electron节点模块(如果找到),如果没有original-fs
,则回退到基本 Node.js模块。fs
function requireWithFallback(electronModule: string, nodeModule: string) {
try {
return require(electronModule);
} catch (err) {}
return require(nodeModule);
}
const fs = requireWithFallback('original-fs', 'fs');
尽可能避免这些情况。
已知的问题
有一些扩展问题可以通过工作区扩展的一些附加功能来解决。下表列出了正在考虑的已知问题:
问题 | 描述 |
---|---|
Cannot access attached devices from Workspace extension | 访问本地连接设备的扩展在远程运行时将无法连接到它们。克服这个问题的一种方法是创建一个配套的 UI 扩展,其工作是访问连接的设备并提供远程扩展也可以调用的命令。 另一种方法是反向隧道,它正在VS Code 存储库问题中进行跟踪。 |
问题和反馈
- 请参阅提示和技巧或常见问题解答。
- 在Stack Overflow上搜索答案。
- 支持一项功能或请求一项新功能、搜索现有问题或报告问题。
- 创建 Dev Containers 模板或功能供其他人使用。
- 为我们的文档或VS Code做出贡献。
- 有关详细信息,请参阅我们的贡献指南。