支持远程开发和 GitHub Codespaces
Visual Studio Code 远程开发 允许您透明地与位于其他计算机(无论是虚拟还是物理)上的源代码和运行时环境进行交互。GitHub Codespaces 是一项服务,它通过托管的云环境扩展了这些功能,这些环境可从 VS Code 和基于浏览器的编辑器进行访问。
为了确保性能,远程开发和 GitHub Codespaces 都透明地在远程运行某些 VS Code 扩展。但是,这可能会对扩展的工作方式产生微妙的影响。虽然许多扩展无需修改即可正常工作,但您可能需要进行一些更改,以确保您的扩展在所有环境中都能正常工作,尽管这些更改通常很小。
本文档总结了扩展作者需要了解的关于远程开发和 Codespaces 的内容,包括扩展 架构,如何在远程工作区或 Codespaces 中 调试扩展,以及关于 扩展无法正常工作时该怎么办 的建议。
架构和扩展种类
为了尽可能地使远程开发或 Codespaces 对用户透明,VS Code 区分了两种扩展:
-
UI 扩展:这些扩展为 VS Code 用户界面做出贡献,并始终在用户的本地计算机上运行。UI 扩展无法直接访问远程工作区中的文件,也无法运行该工作区或计算机上安装的脚本/工具。例如,UI 扩展包括:主题、代码片段、语言语法和键盘映射。
-
工作区扩展:这些扩展在工作区所在的位置运行。在本地工作区中,工作区扩展在本地计算机上运行。在远程工作区中或使用 Codespaces 时,工作区扩展在远程计算机/环境中运行。工作区扩展可以访问工作区中的文件,以提供丰富的多文件语言服务、调试器支持,或对工作区中的多个文件执行复杂操作(直接执行或通过调用脚本/工具)。虽然工作区扩展不侧重于修改 UI,但它们也可以贡献资源管理器、视图和其他 UI 元素。
当用户安装扩展时,VS Code 会根据其种类自动将其安装到正确的位置。如果扩展可以作为任一种类运行,VS Code 将尝试为当前情况选择最佳种类;UI 扩展将在 VS Code 的 本地扩展主机 中运行,而工作区扩展将在 **远程扩展主机** 中运行,该主机位于一个小的 VS Code 服务器 中(如果远程工作区存在),否则将在本地扩展主机中运行(如果本地存在)。为了确保最新的 VS Code 客户端功能可用,服务器需要与 VS Code 客户端版本完全匹配。因此,当您在容器、远程 SSH 主机、使用 Codespaces 或在 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 的基于浏览器的编辑器进行测试和故障排除。如果愿意,您还可以使用 自定义开发容器。
请按照以下步骤操作:
-
导航到 GitHub 上包含您扩展的存储库,并 在 codespace 中打开它,以便在基于浏览器的编辑器中使用它。如果愿意,您也可以 在 VS Code 中打开 codespace。
-
虽然 GitHub Codespaces 的默认映像应该包含大多数扩展所需的所有先决条件,但您可以在新的 VS Code 终端窗口中安装任何其他必需的依赖项(例如,使用
yarn install或sudo apt-get)(⌃⇧` (Windows、Linux Ctrl+Shift+`))。 -
最后,按 F5 或使用 **运行和调试** 视图在 codespace 中启动扩展。
注意:您将无法在出现的窗口中打开扩展源文件夹,但您可以打开 codespace 中的子文件夹或其他位置。
出现的扩展开发主机窗口将包含在 codespace 中运行的扩展,并且调试器已附加到它。
在自定义开发容器中进行调试
请按照以下步骤操作:
-
要在本地使用开发容器,请 安装和配置 Dev Containers 扩展,并使用 **文件 > 打开... / 打开文件夹...** 在 VS Code 中本地打开您的源代码。要改用 Codespaces,请导航到 GitHub 上包含您扩展的存储库,并 在 codespace 中打开它,以便在基于浏览器的编辑器中使用它。如果愿意,您也可以 在 VS Code 中打开 codespace。
-
从命令面板(F1)中选择 **Dev Containers: Add Dev Container Configuration Files...** 或 **Codespaces: Add Dev Container Configuration Files...**,然后选择 **Node.js & TypeScript**(如果您不使用 TypeScript,则选择 Node.js)来添加所需的容器配置文件。
-
可选:在此命令运行后,您可以修改
.devcontainer文件夹的内容,以包含其他构建或运行时要求。有关详细信息,请参阅深入的 创建开发容器 文档。 -
运行 **Dev Containers: Reopen in Container** 或 **Codespaces: Add Dev Container Configuration Files...**,片刻之后,VS Code 将设置容器并连接。现在,您将能够像在本地一样在容器内开发源代码。
-
在新 VS Code 终端窗口中运行
yarn install或npm install(⌃⇧` (Windows、Linux Ctrl+Shift+`)),以确保安装了 Node.js 原生依赖项的 Linux 版本。您也可以安装其他操作系统或运行时依赖项,但最好也将它们添加到.devcontainer/Dockerfile中,以便在重新构建容器时可用。 -
最后,按 F5 或使用 **运行和调试** 视图在此容器中启动扩展并附加调试器。
注意:您将无法在出现的窗口中打开扩展源文件夹,但您可以打开容器中的子文件夹或其他位置。
出现的扩展开发主机窗口将包含在步骤 2 中定义的容器中运行的扩展,并且调试器已附加到它。
使用 SSH 进行调试
按照步骤进行
-
在 安装和配置 Remote - SSH 扩展后,从 VS Code 中的命令面板(F1)中选择 **Remote-SSH: Connect to Host...** 来连接到主机。
-
连接后,使用 **文件 > 打开... / 打开文件夹...** 选择远程扩展源文件夹,或从命令面板(F1)中选择 **Git: Clone** 来克隆并在远程主机上打开它。
-
在新 VS Code 终端窗口中安装任何可能缺失的必需依赖项(例如,使用
yarn install或apt-get)(⌃⇧` (Windows、Linux Ctrl+Shift+`))。 -
最后,按 F5 或使用 **运行和调试** 视图在远程主机上启动扩展并附加调试器。
注意:您将无法在出现的窗口中打开扩展源文件夹,但您可以打开 SSH 主机中的子文件夹或其他位置。
出现的扩展开发主机窗口将包含在 SSH 主机上运行的扩展,并且调试器已附加到它。
使用 WSL 进行调试
请按照以下步骤操作:
-
在 安装和配置 WSL 扩展后,从 VS Code 中的命令面板(F1)中选择 **WSL: New Window**。
-
在出现的窗口中,使用 **文件 > 打开... / 打开文件夹...** 选择远程扩展源文件夹,或从命令面板(F1)中选择 **Git: Clone** 来克隆并在 WSL 中打开它。
提示:您可以选择
/mnt/c文件夹来访问 Windows 上的任何克隆的源代码。 -
在新 VS Code 终端窗口中安装任何可能缺失的必需依赖项(例如,使用
apt-get)(⌃⇧` (Windows、Linux Ctrl+Shift+`))。您至少需要运行yarn install或npm install来确保 Linux 版本的 Node.js 原生依赖项可用。 -
最后,按 F5 或使用 **运行和调试** 视图启动扩展并像本地一样附加调试器。
注意:您将无法在出现的窗口中打开扩展源文件夹,但您可以打开 WSL 中的子文件夹或其他位置。
出现的扩展开发主机窗口将包含在 WSL 中运行的扩展,并且调试器已附加到它。
安装扩展的开发版本
任何时候 VS Code 在 SSH 主机、容器或 WSL 中或通过 GitHub Codespaces 自动安装扩展时,都会使用 Marketplace 版本(而不是您本地计算机上已安装的版本)。
虽然这在大多数情况下是合理的,但您可能希望测试(或共享)扩展的未发布版本,而无需设置调试环境。要安装扩展的未发布版本,您可以将扩展打包为 VSIX 并将其手动安装到已连接到正在运行的远程环境的 VS Code 窗口中。
请按照以下步骤操作:
- 如果这是一个已发布的扩展,您可能希望在
settings.json中添加"extensions.autoUpdate": false,以防止它自动更新到最新的 Marketplace 版本。 - 接下来,使用
vsce package将您的扩展打包为 VSIX。 - 连接到 codespace、Dev Containers、SSH 主机或 WSL 环境。
- 使用扩展视图 **更多操作**(
...)菜单中可用的 **从 VSIX 安装...** 命令,将扩展安装到此特定窗口(而不是本地窗口)。 - 提示时重新加载。
提示:安装后,您可以使用 **Developer: Show Running Extensions** 命令来查看 VS Code 是在本地还是远程运行扩展。
处理远程扩展的依赖项
扩展可以依赖其他扩展的 API。例如:
- 扩展可以在其
activate函数中导出 API。 - 该 API 将可供在同一扩展主机中运行的所有扩展使用。
- 使用者扩展在其
package.json中使用extensionDependencies属性声明它们依赖于提供扩展。
当所有扩展都本地运行并共享同一扩展主机时,扩展依赖项可以正常工作。
在处理远程场景时,有可能一个在远程运行的扩展依赖于一个在本地运行的扩展。例如,本地扩展公开了一个对远程扩展功能至关重要的命令。在这种情况下,我们建议远程扩展将本地扩展声明为 extensionDependency,但问题是扩展运行在两个不同的扩展主机上,这意味着提供者的 API 对使用者不可用。因此,要求提供扩展完全放弃导出任何 API 的能力,通过在其扩展的 package.json 中使用 "api": "none"。扩展仍然可以通过 VS Code 命令(异步)进行通信。
这可能看起来是对提供扩展不必要的严格限制,但使用 "api": "none" 的扩展只放弃了从其 activate 方法返回 API 的能力。在其他扩展主机上运行的使用者扩展仍然可以依赖它们,并且将被激活。
常见问题
VS Code 的 API 设计成无论您的扩展位于何处,都能在正确的位置自动运行。考虑到这一点,有一些 API 可以帮助您避免意外行为。
错误的执行位置
如果您的扩展功能不符合预期,可能是因为它在错误的位置运行。最常见的情况是,您期望扩展仅在本地运行,但它却在远程运行。您可以使用命令面板(F1)中的 **Developer: Show Running Extensions** 命令来查看扩展的运行位置。
如果 **Developer: Show Running Extensions** 命令显示 UI 扩展被错误地视为工作区扩展或反之,请尝试在扩展的 package.json 中设置 extensionKind 属性,如 扩展种类部分所述。
您可以使用 remote.extensionKind 设置 快速 **测试** 更改扩展种类的效果。此设置是扩展 ID 到扩展种类的映射。例如,如果您想强制 Azure Databases 扩展成为 UI 扩展(而不是其默认的工作区扩展),并将 Remote - SSH: Editing Configuration Files 扩展成为工作区扩展(而不是其默认的 UI 扩展),您将设置:
{
"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)或特定 OS 文件夹(例如 Linux 上的 ~/.config/Code)的存在来持久化数据,则可能会遇到问题。幸运的是,更新您的扩展并避免这些挑战应该很简单。
如果您持久化简单的键值对,您可以使用 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);
));
}
在机器之间同步用户全局状态
如果您的扩展需要在不同机器之间保留某些用户状态,请使用 vscode.ExtensionContext.globalState.setKeysForSync 将状态提供给 Settings Sync。这有助于避免在多台机器上向用户显示相同的欢迎或更新页面。
在 扩展功能 主题中有一个使用 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);
}
使用剪贴板
历史上,扩展作者使用 clipboardy 等 Node.js 模块与剪贴板进行交互。不幸的是,如果您在工作区扩展中使用这些模块,它们将使用远程剪贴板而不是用户的本地剪贴板。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 node 模块,以允许现有扩展正常工作。您可以使用 URI 调用该模块,VS Code 将在该端显示 URI 的默认应用程序。但是,这不是一个完整的实现,因为选项不受支持,并且不返回 child_process 对象。
我们建议扩展利用 vscode.env.openExternal 方法来启动本地操作系统上给定 URI 的默认注册应用程序,而不是依赖第三方 node 模块。更好的是,vscode.env.openExternal 自动进行本地端口转发! 您可以使用它来指向远程计算机或 codespace 上的本地 Web 服务器,即使该端口在外部被阻止,也可以提供内容。
注意:目前 Codespaces 基于浏览器的编辑器中的转发机制仅支持HTTP 和 HTTPS 请求。但是,当从 VS Code 连接到 codespace 时,您可以与任何 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.js.cn'));
// Example 2 - Open an auto-forwarded localhost HTTP server.
vscode.env.openExternal(vscode.Uri.parse('https://: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 连接到 codespace 时,您可以与任何 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(`https://:${dynamicServerPort}`));
// ... do something with the fullUri ...
}));
}
重要的是要注意,API 返回的 URI可能根本不引用 localhost,因此您应该完整地使用它。这对于 Codespaces 的基于浏览器的编辑器尤其重要,因为 localhost 在其中无法使用。
回调和 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 中运行此示例时,它会连接一个 vscode:// 或 vscode-insiders:// URI,该 URI 可用作身份验证提供者的回调。在 Codespaces 的基于浏览器的编辑器中运行时,它会连接一个 https://*.github.dev URI,而无需进行任何代码更改或特殊条件。
虽然 OAuth 超出了本文档的范围,但请注意,如果您将此示例改编为真实的身份验证提供者,您可能需要构建一个代理服务来处理该提供者。这是因为并非所有提供者都允许 vscode:// 回调 URI,而另一些则不允许在 HTTPS 回调中使用通配符主机名。我们还建议尽可能使用 OAuth 2.0 授权码 PKCE 流程(例如,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,这些 API 供其他扩展使用(通过 vscode.extension.getExtension(extensionName).exports)。虽然如果所有涉及的扩展都在同一侧(全部是 UI 扩展或全部是工作区扩展),这些 API 将会正常工作,但它们在 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 指南。
使用 Webview API
与剪贴板 API 一样,Webview API 始终在用户的本地计算机或浏览器中运行,即使它从工作区扩展中使用。这意味着许多基于 Webview 的扩展即使在远程工作区或 Codespaces 中使用也应该可以正常工作。但是,有一些注意事项需要注意,以确保您的 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>`;
使用消息传递 API 为动态 Webview 内容
VS Code Webview 包括一个 消息传递 API,允许您动态更新 Webview 内容,而无需使用本地 Web 服务器。即使您的扩展正在运行一些您想与之交互以更新 Webview 内容的本地 Web 服务,您也可以从扩展本身执行此操作,而不是直接从 HTML 内容中执行。
这是远程开发和 GitHub Codespaces 的一个重要模式,以确保您的 Webview 代码能在 VS Code 和 Codespaces 的基于浏览器的编辑器中正常工作。
为什么使用消息传递而不是 localhost Web 服务器?
备用模式是通过在 iframe 中提供 Web 内容,或者让 webview 内容直接与本地主机服务器进行交互。不幸的是,默认情况下,webview 中的 localhost 会解析到开发者的本地机器。这意味着,对于远程运行的工作区扩展,它创建的 webview 将无法访问扩展启动的本地服务器。即使您使用机器的 IP,默认情况下,云虚拟机或容器中的端口也通常会被阻止。即使这在 VS Code 中有效,在基于浏览器的 Codespaces 编辑器中也无效。
当使用 Remote - SSH 扩展时,这里有一个问题的说明,但这个问题也存在于 Dev Containers 和 GitHub Codespaces 中。

如果可能,**您应该避免这样做**,因为它会显著地使您的扩展复杂化。消息传递 API 可以在没有这些麻烦的情况下实现相同的用户体验。扩展本身将在远程的 VS Code Server 中运行,因此它可以透明地与您的扩展响应 webview 传递给它的任何消息而启动的任何 Web 服务器进行交互。
从 webview 使用 localhost 的解决方法
如果出于某种原因无法使用消息传递 API,有两种选项可以在 VS Code 中与 Remote Development 和 GitHub Codespaces 扩展一起使用。
每个选项都允许 webview 内容通过 VS Code 用于与 VS Code Server 通信的相同通道进行路由。例如,如果我们更新上一节中 Remote - SSH 的说明图,您将得到以下内容:

选项 1 - 使用 asExternalUri
VS Code 1.40 引入了 vscode.env.asExternalUri API,允许扩展以编程方式将本地 http 和 https 请求远程转发。当您的扩展在 VS Code 中运行时,您可以使用此 API 将请求从 webview 转发到 localhost Web 服务器。
使用该 API 获取 iframe 的完整 URI 并将其添加到 HTML 中。您还需要在 webview 中启用脚本,并为 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(`https://:${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 编辑器**,则可以使用 webview API 中提供的 portMapping 选项。(此方法也可与 VS Code 客户端的 Codespaces 一起使用,但在浏览器中无效)。
要使用端口映射,请在创建 webview 时传入 portMapping 对象。
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="https://:${LOCAL_STATIC_PORT}/canvas.png">
</body>
</html>`;
在此示例中,对于远程和本地情况,对 https://:3000 的任何请求都将自动映射到 Express.js Web 服务器正在运行的动态端口。
使用原生 Node.js 模块
随 VS Code 扩展一起打包(或动态获取)的原生模块必须使用 Electron 的 electron-rebuild 进行重新编译。然而,VS Code Server 运行标准(非 Electron)版本的 Node.js,这可能导致在远程使用二进制文件时失败。
要解决此问题
- 在 Node.js 的“modules”版本中包含(或动态获取)这两个二进制文件集(Electron 和标准 Node.js),VS Code 会随之发布。
- 检查
vscode.extensions.getExtension('your.extensionId').extensionKind === vscode.ExtensionKind.Workspace以根据扩展是在远程还是本地运行来设置正确的二进制文件。 - 您可能还希望通过遵循类似的逻辑来同时支持非 x86_64 目标和 Alpine Linux。
您可以通过转到**帮助 > 开发者工具**并输入 process.versions.modules 在控制台中找到 VS Code 使用的“modules”版本。但是,为确保原生模块在不同的 Node.js 环境中无缝工作,您可能希望针对所有可能的 Node.js “modules”版本和您想要支持的平台(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_64 glibc 特定的原生代码或运行时,这些代码在这些架构/操作系统上会失败。
例如,您的扩展可能只包含原生模块或运行时的 x86_64 编译版本。对于 Alpine Linux,由于 Alpine Linux (musl) 和其他发行版 (glibc) 的 libc 实现方式之间存在根本性差异,因此包含的原生代码或运行时可能无法正常工作。
解决此问题
-
如果您动态获取已编译的代码,可以通过使用
process.arch检测非 x86_64 目标并下载为正确架构编译的版本来添加支持。如果您将所有受支持架构的二进制文件包含在扩展中,则可以使用此逻辑来使用正确的二进制文件。 -
对于 Alpine Linux,您可以使用
await fs.exists('/etc/alpine-release')检测操作系统,并再次下载或使用基于musl的操作系统的正确二进制文件。 -
如果您宁愿不支持这些平台,您可以使用相同的逻辑来提供一个良好的错误消息。
需要注意的是,一些第三方 npm 模块包含可能导致此问题的原生代码。因此,在某些情况下,您可能需要与 npm 模块作者合作以添加其他编译目标。
避免使用 Electron 模块
虽然依赖 Electron 内置模块或扩展 API 未公开的 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');
尽量避免这些情况。
已知问题
有几个扩展问题可以通过为工作区扩展添加一些功能来解决。下表是正在考虑中的已知问题列表。
| 问题 | 描述 |
|---|---|
| 工作区扩展无法访问已连接的设备 | 访问本地已连接设备的扩展在远程运行时将无法连接到它们。克服此问题的一种方法是创建一个伴随的 UI 扩展,该扩展负责访问已连接的设备并提供远程扩展也可以调用的命令。 另一种方法是反向隧道,这正在 VS Code 仓库问题中跟踪。 |