🚀 在 VS Code 中

支援遠端開發和 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 或在 Windows Subsystem for Linux (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: Add Dev Container Configuration Files...Codespaces: Add Dev Container Configuration Files...,然後選取 Node.js & TypeScript(如果您未使用 TypeScript,則選取 Node.js)以新增必要的容器組態檔案。

  3. 選用:執行此命令後,您可以修改 .devcontainer 資料夾的內容,以包含其他建置或執行階段需求。請參閱深入的 建立開發容器 文件以取得詳細資訊。

  4. 執行 Dev Containers: Reopen in ContainerCodespaces: Add Dev Container Configuration Files...,稍待片刻,VS Code 將設定容器並連線。現在,您將能夠像在本機案例中一樣,從容器內部開發您的原始碼。

  5. 在新的 VS Code 終端機視窗中執行 yarn installnpm install (⌃⇧` (Windows、Linux Ctrl+Shift+`)) 以確保已安裝 Linux 版本的 Node.js 原生相依性。您也可以安裝其他作業系統或執行階段相依性,但您可能也想將這些相依性新增至 .devcontainer/Dockerfile,以便在您重建容器時可以使用它們。

  6. 最後,按下 F5 或使用執行和偵錯檢視,在這個相同的容器內啟動擴充功能並附加偵錯工具。

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

出現的擴充功能開發主機視窗將包含您的擴充功能,該擴充功能在您在步驟 2 中定義的容器中執行,並附加了偵錯工具。

使用 SSH 偵錯

請依照步驟操作

  1. 安裝和設定 Remote - SSH 擴充功能後,從 VS Code 中的命令面板 (F1) 選取 Remote-SSH: Connect to Host... 以連線到主機。

  2. 連線後,使用 檔案 > 開啟... / 開啟資料夾... 選取包含擴充功能原始碼的遠端資料夾,或從命令面板 (F1) 選取 Git: Clone 以複製並在遠端主機上開啟它。

  3. 在新的 VS Code 終端機視窗中安裝任何可能遺失的必要相依性(例如使用 yarn installapt-get)(⌃⇧` (Windows、Linux Ctrl+Shift+`))。

  4. 最後,按下 F5 或使用執行和偵錯檢視,在遠端主機上啟動擴充功能並附加偵錯工具。

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

出現的擴充功能開發主機視窗將包含您的擴充功能,該擴充功能在 SSH 主機上執行,並附加了偵錯工具。

使用 WSL 偵錯

請依照下列步驟操作

  1. 安裝和設定 WSL 擴充功能後,從 VS Code 中的命令面板 (F1) 選取 WSL: New Window

  2. 在出現的新視窗中,使用 檔案 > 開啟... / 開啟資料夾... 選取包含擴充功能原始碼的遠端資料夾,或從命令面板 (F1) 選取 Git: Clone 以複製並在 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. 如果這是已發佈的擴充功能,您可能會想要將 "extensions.autoUpdate": false 新增至 settings.json,以防止它自動更新為最新的 Marketplace 版本。
  2. 接下來,使用 vsce package 將您的擴充功能封裝為 VSIX。
  3. 連線到 codespaceDev ContainersSSH 主機WSL 環境
  4. 使用擴充功能檢視更多動作...)選單中提供的從 VSIX 安裝... 命令,將擴充功能安裝到此特定視窗中(而非本機視窗)。
  5. 出現提示時重新載入。

提示:安裝後,您可以使用 開發人員: 顯示執行中的擴充功能 命令,查看 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 可以協助您避免非預期的行為。

執行位置不正確

如果您的擴充功能未如預期般運作,則可能是執行位置不正確。最常見的情況是,當您預期 UI 擴充功能僅在本機執行時,它卻在遠端執行。您可以使用命令面板 (F1) 中的 開發人員: 顯示執行中的擴充功能 命令,查看擴充功能的執行位置。

如果 開發人員: 顯示執行中的擴充功能 命令顯示 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)或特定作業系統資料夾(例如 Linux 上的 ~/.config/Code)的存在來持續保存資料,則您可能會遇到問題。幸運的是,更新您的擴充功能並避免這些挑戰應該很簡單。

如果您要持續保存簡單的鍵值組,您可以使用 vscode.ExtensionContext.workspaceStatevscode.ExtensionContext.globalState 分別儲存工作區特定或全域狀態資訊。如果您的資料比鍵值組更複雜,則 globalStorageUristorageUri 屬性提供「安全」URI,您可以使用這些 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 node 模組,以允許現有的擴充功能運作。您可以使用 URI 呼叫模組,VS Code 將導致 URI 的預設應用程式出現在用戶端。但是,這不是完整的實作,因為不支援選項,並且不會傳回 child_process 物件。

我們建議擴充功能利用 vscode.env.openExternal 方法,而不是依賴第三方 node 模組,以針對給定的 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.dev.org.tw'));

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

轉送 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(`http://localhost:${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 超出了本文檔的範圍,但請注意,如果您將此範例改編為真正的驗證提供者,您可能需要在提供者前面建置一個 Proxy 服務。這是因為並非所有提供者都允許 vscode:// 回呼 URI,而其他提供者則不允許 HTTPS 的回呼使用萬用字元主機名稱。我們也建議盡可能使用 具有 PKCE 流程的 OAuth 2.0 授權碼(例如,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 作為其啟動的一部分,這些 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 中提供網頁內容,或讓 webview 內容直接與 localhost 伺服器互動。不幸的是,預設情況下,webview 內的 localhost 會解析為開發人員的本機電腦。這表示對於遠端執行的工作區擴充功能,它建立的 webview 將無法存取擴充功能產生的本機伺服器。即使您使用機器的 IP,您要連線的埠通常也會在雲端 VM 或容器中預設遭到封鎖。即使這在 VS Code 中可行,在以瀏覽器為基礎的 Codespaces 編輯器中也無法運作。

以下說明使用 Remote - SSH 擴充功能時發生的問題,但 Dev Containers 和 GitHub Codespaces 也存在相同的問題

Webview problem

如果可以,您應該避免這樣做,因為這會大幅增加擴充功能的複雜性。訊息傳遞 API 可以在不造成這些困擾的情況下,實現相同的使用者體驗。擴充功能本身將在遠端側的 VS Code Server 上執行,因此它可以透明地與擴充功能啟動的任何 Web 伺服器互動,這些伺服器是從 webview 傳遞給它的任何訊息的結果。

從 webview 使用 localhost 的變通方法

如果您由於某些原因無法使用訊息傳遞 API,則有兩個選項可在 VS Code 中的 Remote Development 和 GitHub Codespaces 擴充功能中使用。

每個選項都允許 webview 內容路由通過 VS Code 用於與 VS Code Server 通訊的相同通道。例如,如果我們更新上一節中 Remote - 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(`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 編輯器,則可以使用 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="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,這可能會導致二進位檔在遠端使用時失敗。

若要解決此問題

  1. 在 VS Code 隨附的 Node.js 的「modules」版本中,包含(或動態取得)兩組二進位檔(Electron 和標準 Node.js)。
  2. 檢查是否 vscode.extensions.getExtension('your.extensionId').extensionKind === vscode.ExtensionKind.Workspace,以根據擴充功能是在遠端或本機執行來設定正確的二進位檔。
  3. 您可能也希望同時新增對非 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 的實作方式存在基本差異,因此包含的原生程式碼或執行階段可能無法運作。

若要解決此問題

  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 模組時使用它,如果找不到,則會退回基本 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 存放庫問題中追蹤。

問題與意見反應