參加你附近的 ,瞭解 VS Code 中的 AI 輔助開發。

支援遠端開發和 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 客戶端功能可用,伺服器需要與 VS Code 客戶端版本完全匹配。因此,當您在容器中、遠端 SSH 主機上、使用 Codespaces 或在適用於 Linux 的 Windows 子系統 (WSL) 中開啟資料夾時,遠端開發或 GitHub Codespaces 擴充套件會自動安裝(或更新)伺服器。(VS Code 還會自動管理伺服器的啟動和停止,因此使用者不會意識到它的存在。)

Architecture diagram

VS Code API 設計為從 UI 或工作區擴充套件呼叫時自動在正確的機器(本地或遠端)上執行。但是,如果您的擴充套件使用 VS Code 未提供的 API(例如使用 Node API 或執行 shell 指令碼),則在遠端執行時可能無法正常工作。我們建議您測試擴充套件的所有功能在本地和遠端工作區中是否都正常工作。

除錯擴充套件

雖然您可以在遠端環境中安裝開發版本的擴充套件進行測試,但如果遇到問題,您可能希望直接在遠端環境中除錯您的擴充套件。在本節中,我們將介紹如何在GitHub Codespaces本地容器SSH 主機WSL 中編輯、啟動和除錯您的擴充套件。

通常,測試的最佳起點是使用限制埠訪問的遠端環境(例如 Codespaces、容器或具有限制性防火牆的遠端 SSH 主機),因為在這些環境中工作的擴充套件通常在限制性較小的環境中(如 WSL)也能工作。

使用 GitHub Codespaces 除錯

GitHub Codespaces 預覽版中除錯您的擴充套件可能是一個很好的起點,因為您可以使用 VS Code 和 Codespaces 基於瀏覽器的編輯器進行測試和故障排除。如果願意,您也可以使用自定義開發容器

請按照以下步驟操作:

  1. 導航到 GitHub 上包含您的擴充套件的倉庫,然後在 codespace 中開啟它,以便在基於瀏覽器的編輯器中進行操作。如果您願意,也可以在 VS Code 中開啟 codespace

  2. 雖然 GitHub Codespaces 的預設映象應該包含大多數擴充套件所需的所有先決條件,但您可以在新的 VS Code 終端視窗中安裝任何其他所需的依賴項(例如,使用 yarn installsudo apt-get)(⌃⇧` (Windows, Linux Ctrl+Shift+`))。

  3. 最後,按 F5 或使用執行和除錯檢視在 codespace 內啟動擴充套件。

    注意:您將無法在出現的視窗中開啟擴充套件原始碼資料夾,但可以開啟子資料夾或 codespace 中的其他位置。

出現的擴充套件開發主機視窗將包含您的擴充套件,它在 codespace 中執行,並且偵錯程式已附加到它。

在自定義開發容器中除錯

請按照以下步驟操作:

  1. 要在本地使用開發容器,請安裝並配置 Dev Containers 擴充套件,然後使用 檔案 > 開啟... / 開啟資料夾... 在 VS Code 中本地開啟您的原始碼。要改用 Codespaces,請導航到 GitHub 上包含您的擴充套件的儲存庫,然後在 Codespace 中開啟它,以便在基於瀏覽器的編輯器中進行操作。如果您願意,也可以在 VS Code 中開啟 Codespace

  2. 從命令面板(F1)中選擇 Dev Containers: 新增 Dev Container 配置檔案...Codespaces: 新增 Dev Container 配置檔案...,然後選擇 Node.js & TypeScript(如果您不使用 TypeScript,則選擇 Node.js)以新增所需的容器配置檔案。

  3. 可選:此命令執行後,您可以修改 .devcontainer 資料夾的內容,以包含額外的構建或執行時要求。有關詳細資訊,請參閱深入的建立開發容器文件。

  4. 執行 Dev Containers: 在容器中重新開啟Codespaces: 新增 Dev Container 配置檔案...,片刻之後,VS Code 將設定容器並連線。現在您可以在容器內部開發您的原始碼,就像在本地一樣。

  5. 在新的 VS Code 終端視窗中執行 yarn installnpm install⌃⇧` (Windows, Linux Ctrl+Shift+`)),以確保安裝了 Linux 版本的 Node.js 本機依賴項。您還可以安裝其他作業系統或執行時依賴項,但您可能也希望將這些依賴項新增到 .devcontainer/Dockerfile 中,以便在重新構建容器時它們可用。

  6. 最後,按 F5 或使用執行和除錯檢視在此同一容器內啟動擴充套件並附加偵錯程式。

    注意:您將無法在出現的視窗中開啟擴充套件原始碼資料夾,但可以開啟子資料夾或容器中的其他位置。

出現的擴充套件開發主機視窗將包含您的擴充套件,它在您在步驟 2 中定義的容器中執行,並且偵錯程式已附加到它。

使用 SSH 除錯

請按以下步驟操作

  1. 安裝並配置遠端 - SSH 擴充套件後,從 VS Code 命令面板(F1)中選擇 Remote-SSH: 連線到主機... 以連線到主機。

  2. 連線後,使用 檔案 > 開啟... / 開啟資料夾... 選擇包含您的擴充套件原始碼的遠端資料夾,或者從命令面板(F1)中選擇 Git: 克隆 以克隆它並在遠端主機上開啟它。

  3. 在新的 VS Code 終端視窗中安裝任何可能缺失的依賴項(例如,使用 yarn installapt-get)(⌃⇧` (Windows, Linux Ctrl+Shift+`))。

  4. 最後,按 F5 或使用執行和除錯檢視在遠端主機上啟動擴充套件並附加偵錯程式。

    注意:您將無法在出現的視窗中開啟擴充套件原始碼資料夾,但可以開啟子資料夾或 SSH 主機上的其他位置。

出現的擴充套件開發主機視窗將包含您的擴充套件,它在 SSH 主機上執行,並且偵錯程式已附加到它。

使用 WSL 除錯

請按照以下步驟操作:

  1. 安裝並配置 WSL 擴充套件後,從 VS Code 命令面板(F1)中選擇 WSL: 新視窗

  2. 在新出現的視窗中,使用 檔案 > 開啟... / 開啟資料夾... 選擇包含您的擴充套件原始碼的遠端資料夾,或者從命令面板(F1)中選擇 Git: 克隆 以克隆它並在 WSL 中開啟它。

    提示:您可以選擇 /mnt/c 資料夾來訪問您在 Windows 側的任何克隆原始碼。

  3. 在新的 VS Code 終端視窗中安裝任何可能缺失的依賴項(例如,使用 apt-get)(⌃⇧` (Windows, Linux Ctrl+Shift+`))。您至少需要執行 yarn installnpm install 來確保 Linux 版本的本機 Node.js 依賴項可用。

  4. 最後,按 F5 或使用執行和除錯檢視啟動擴充套件並像在本地一樣附加偵錯程式。

    注意:您將無法在出現的視窗中開啟擴充套件原始碼資料夾,但可以開啟子資料夾或 WSL 中的其他位置。

出現的擴充套件開發主機視窗將包含您的擴充套件,它在 WSL 中執行,並且偵錯程式已附加到它。

安裝開發版本的擴充套件

無論 VS Code 何時自動在 SSH 主機、容器內、WSL 中或透過 GitHub Codespaces 安裝擴充套件,都會使用 Marketplace 版本(而不是您本地機器上已安裝的版本)。

雖然這在大多數情況下都是合理的,但您可能希望使用(或共享)未釋出的擴充套件版本進行測試,而無需設定除錯環境。要安裝未釋出的擴充套件版本,您可以將擴充套件打包為 VSIX 並手動將其安裝到已連線到正在執行的遠端環境的 VS Code 視窗中。

請按照以下步驟操作:

  1. 如果這是一個已釋出的擴充套件,您可能需要在 settings.json 中新增 "extensions.autoUpdate": false 以防止它自動更新到最新的 Marketplace 版本。
  2. 接下來,使用 vsce package 將您的擴充套件打包為 VSIX。
  3. 連線到 codespace開發容器SSH 主機WSL 環境
  4. 使用“擴充套件”檢視的更多操作...)選單中可用的 從 VSIX 安裝... 命令,將擴充套件安裝到此特定視窗(而非本地視窗)。
  5. 出現提示時重新載入。

提示:安裝後,您可以使用開發者:顯示正在執行的擴充套件命令檢視 VS Code 是在本地還是遠端執行擴充套件。

處理遠端擴充套件的依賴項

擴充套件可以依賴其他擴充套件的 API。例如:

  • 一個擴充套件可以從其 activate 函式中匯出 API。
  • 此 API 將可供在同一擴充套件主機中執行的所有擴充套件使用。
  • 消費擴充套件在其 package.json 中使用 extensionDependencies 屬性宣告它們依賴於提供擴充套件。

當所有擴充套件都在本地執行並共享同一擴充套件主機時,擴充套件依賴項工作正常。

在處理遠端場景時,遠端執行的擴充套件可能對本地執行的擴充套件具有擴充套件依賴性。例如,本地擴充套件公開了一個對遠端擴充套件功能至關重要的命令。在這種情況下,我們建議遠端擴充套件將本地擴充套件宣告為 extensionDependency,但問題是這些擴充套件執行在兩個不同的擴充套件主機上,這意味著提供者提供的 API 對消費者不可用。因此,要求提供者擴充套件透過在其擴充套件的 package.json 中使用 "api": "none" 完全放棄匯出任何 API 的能力。擴充套件仍然可以使用 VS Code 命令(它們是非同步的)進行通訊。

這似乎是對提供擴充套件的不必要的嚴格限制,但使用 "api": "none" 的擴充套件只放棄了從其 activate 方法返回 API 的能力。在其他擴充套件主機上執行的消費者擴充套件仍然可以依賴它們並被啟用。

常見問題

VS Code 的 API 設計為無論您的擴充套件位於何處,都能自動在正確的位置執行。考慮到這一點,有一些 API 可以幫助您避免意外行為。

錯誤的執行位置

如果您的擴充套件未按預期執行,它可能在錯誤的位置執行。最常見的情況是,當您期望擴充套件僅在本地執行時,它卻在遠端執行。您可以使用命令面板(F1)中的開發者:顯示正在執行的擴充套件命令來檢視擴充套件的執行位置。

如果開發者:顯示正在執行的擴充套件命令顯示 UI 擴充套件被錯誤地視為工作區擴充套件,反之亦然,請嘗試按照擴充套件型別部分的說明,在您擴充套件的package.json中設定 extensionKind 屬性。

您可以使用 remote.extensionKind 設定快速測試更改擴充套件型別的效果。此設定是將擴充套件 ID 對映到擴充套件型別的對映。例如,如果您希望強制 Azure 資料庫擴充套件成為 UI 擴充套件(而不是其工作區預設值),並且 遠端 - SSH: 編輯配置檔案擴充套件成為工作區擴充套件(而不是其 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)或某些作業系統資料夾(例如 Linux 上的 ~/.config/Code)的存在來持久化資料,您可能會遇到問題。幸運的是,更新您的擴充套件並避免這些挑戰應該很簡單。

如果您正在持久化簡單的鍵值對,則可以使用 vscode.ExtensionContext.workspaceStatevscode.ExtensionContext.globalState 分別儲存工作區特定或全域性狀態資訊。如果您的資料比鍵值對更復雜,則 globalStorageUristorageUri 屬性提供“安全”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 將狀態提供給設定同步。這有助於防止在多臺機器上向用戶顯示相同的歡迎或更新頁面。

擴充套件功能主題中有一個使用 setKeysforSync 的示例。

持久化金鑰

如果您的擴充套件需要持久化密碼或其他秘密,您可能需要使用 Visual Studio Code 的SecretStorage API,它提供了一種安全地在檔案系統上儲存文字並透過加密進行備份的方法。例如,在桌面上,我們使用 Electron 的safeStorage API 在將秘密儲存到檔案系統之前對其進行加密。該 API 將始終在客戶端儲存秘密,但無論您的擴充套件在哪裡執行,您都可以使用此 API 並檢索相同的秘密值。

注意:此 API 是持久化密碼和秘密的推薦方式。您不應使用 vscode.ExtensionContext.workspaceStatevscode.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 物件。

我們建議擴充套件不依賴第三方 Node 模組,而是利用 vscode.env.openExternal 方法,為給定 URI 在您的本地作業系統上啟動預設註冊的應用程式。更好的是,vscode.env.openExternal 會自動進行 localhost 埠轉發! 您可以使用它指向遠端機器或 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.com.tw'));

      // 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>'));
    })
  );
}

轉發 localhost

vscode.env.openExternal 中的 localhost 轉發機制很有用,但有時您可能希望在不實際啟動新的瀏覽器視窗或應用程式的情況下轉發某些內容。這就是 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 處理程式的常見用例是實現使用 OAuth 2.0 身份驗證提供程式(例如 Azure AD)的服務登入。但是,它可以用在您希望外部應用程式或瀏覽器向您的擴充套件傳送資訊的任何場景中。

VS Code 中的遠端開發和 Codespaces 擴充套件將透明地處理將 URI 傳遞給您的擴充套件,無論它實際執行在哪裡(本地或遠端)。但是,vscode:// URI 將無法與 Codespaces 基於瀏覽器的編輯器一起使用,因為在瀏覽器等中開啟這些 URI 會嘗試將它們傳遞給本地 VS Code 客戶端而不是基於瀏覽器的編輯器。幸運的是,這可以透過使用 vscode.env.asExternalUri API 輕鬆解決。

讓我們結合使用 vscode.window.registerUriHandlervscode.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,可用作身份驗證提供程式的回撥。在 Codespaces 基於瀏覽器的編輯器中執行時,它會連線一個 https://*.github.dev URI,無需任何程式碼更改或特殊條件。

雖然 OAuth 超出本文件的範圍,但請注意,如果您將此示例適配到真實的身份驗證提供程式,您可能需要在提供程式前面構建一個代理服務。這是因為並非所有提供程式都允許 vscode:// 回撥 URI,而其他提供程式則不允許透過 HTTPS 進行回撥的萬用字元主機名。我們還建議儘可能使用 OAuth 2.0 授權碼與 PKCE 流程(例如,Azure AD 支援 PKCE)以提高回撥的安全性。

在遠端或 Codespaces 瀏覽器編輯器中執行時的不同行為

在某些情況下,您的工作區擴充套件可能需要在遠端執行時改變行為。在其他情況下,您可能希望在 Codespaces 基於瀏覽器的編輯器中執行時改變其行為。VS Code 提供了三個 API 來檢測這些情況:vscode.env.uiKindextension.extensionKindvscode.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 指南

使用 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 內容直接與 localhost 伺服器互動。不幸的是,預設情況下,Webview 中的 localhost 將解析為開發人員的本地機器。這意味著對於遠端執行的工作區擴充套件,它建立的 Webview 將無法訪問該擴充套件啟動的本地伺服器。即使您使用機器的 IP,您連線的埠通常在雲 VM 或容器中預設被阻止。即使這在 VS Code 中有效,它在 Codespaces 基於瀏覽器的編輯器中也無法工作。

以下是使用遠端 - SSH 擴充套件時問題的說明,但該問題也存在於開發容器和 GitHub Codespaces 中

Webview problem

如果可能,您應該避免這樣做,因為它會極大地使您的擴充套件複雜化。訊息傳遞 API 可以在不帶來這些麻煩的情況下實現相同型別的使用者體驗。擴充套件本身將在遠端端的 VS Code Server 中執行,因此它可以透明地與您的擴充套件因從 Webview 傳遞的任何訊息而啟動的任何 Web 伺服器進行互動。

從 Webview 使用 localhost 的解決方法

如果您出於某種原因無法使用訊息傳遞 API,則有兩種方法可以在 VS Code 中與遠端開發和 GitHub Codespaces 擴充套件一起使用。

每個選項都允許 Webview 內容透過 VS Code 用於與 VS Code Server 通訊的相同通道進行路由。例如,如果我們將上一節中關於遠端 - SSH 的圖示進行更新,您將得到以下內容:

Webview Solution

選項 1 - 使用 asExternalUri

VS Code 1.40 引入了 vscode.env.asExternalUri API,允許擴充套件以程式設計方式遠端轉發本地 httphttps 請求。當您的擴充套件在 VS Code 中執行時,您可以使用此相同的 API 將請求從 Webview 轉發到 localhost Web 伺服器。

使用 API 獲取 iframe 的完整 URI 並將其新增到您的 HTML 中。您還需要在您的 webview 中啟用指令碼並將 CSP 新增到您的 HTML 內容中。

// 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,這可能導致二進位制檔案在遠端使用時失敗。

為解決這個問題

  1. 為 VS Code 附帶的 Node.js “模組”版本,同時包含(或動態獲取)兩組二進位制檔案(Electron 和標準 Node.js)。
  2. 檢查 vscode.extensions.getExtension('your.extensionId').extensionKind === vscode.ExtensionKind.Workspace 以根據擴充套件是在遠端還是本地執行來設定正確的二進位制檔案。
  3. 您可能還需要透過遵循類似的邏輯,同時新增對非 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_64 glibc 特定的原生程式碼或執行時,這些程式碼或執行時將在這些架構/作業系統上失敗。

例如,您的擴充套件可能只包含原生模組或執行時的 x86_64 編譯版本。對於 Alpine Linux,由於 Alpine Linux (musl) 中 libc 的實現方式與其他發行版 (glibc) 之間存在根本差異,包含的原生程式碼或執行時可能無法工作。

為解決此問題

  1. 如果您正在動態獲取編譯程式碼,可以透過使用 process.arch 檢測非 x86_64 目標並下載為正確架構編譯的版本來新增支援。如果您在擴充套件中包含所有支援架構的二進位制檔案,則可以使用此邏輯來使用正確的二進位制檔案。

  2. 對於 Alpine Linux,您可以使用 await fs.exists('/etc/alpine-release') 檢測作業系統,並再次下載或使用基於 musl 作業系統的正確二進位制檔案。

  3. 如果您不想支援這些平臺,您可以使用相同的邏輯來提供一個好的錯誤訊息。

需要注意的是,一些第三方 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');

儘可能避免這些情況。

已知問題

有一些擴充套件問題可以透過為工作區擴充套件新增一些功能來解決。下表列出了正在考慮的已知問題

問題 描述
無法從工作區擴充套件訪問連線的裝置 訪問本地連線裝置的擴充套件在遠端執行時將無法連線到它們。克服此問題的一種方法是建立一個伴隨的 UI 擴充套件,其任務是訪問連線的裝置並提供遠端擴充套件也可以呼叫的命令。
另一種方法是反向隧道,該方法正在 VS Code 倉庫問題中進行跟蹤。

問題和反饋