Remote Development と GitHub Codespaces のサポート

Visual Studio Code Remote Development を使用すると、他のマシン(仮想か物理かを問わず)上にあるソースコードや実行環境を、透過的に操作できます。GitHub Codespaces は、VS Code およびブラウザベースのエディタからアクセス可能な、管理されたクラウドホスト環境によってこれらの機能を拡張するサービスです。

パフォーマンスを確保するため、Remote Development と GitHub Codespaces はどちらも特定の VS Code 拡張機能をリモートで透過的に実行します。しかし、これは拡張機能の動作に微妙な影響を与える可能性があります。多くの拡張機能は修正なしで動作しますが、すべての環境で適切に動作させるために変更が必要になる場合があります(ただし、多くの場合、変更は非常に軽微です)。

この記事では、拡張機能の開発者が知っておくべき Remote Development と Codespaces に関する情報をまとめました。これには、拡張機能のアーキテクチャ、リモートワークスペースや Codespaces での拡張機能のデバッグ方法、および拡張機能が正しく動作しない場合の対処法に関する推奨事項が含まれます。

アーキテクチャと拡張機能の種類

Remote Development や Codespaces での操作をユーザーにとって可能な限り透過的にするため、VS Code は2種類の拡張機能を区別しています。

  • UI 拡張機能: これらの拡張機能は VS Code のユーザーインターフェイスに寄与するものであり、常にユーザーのローカルマシン上で実行されます。UI 拡張機能は、リモートワークスペース内のファイルに直接アクセスしたり、そのワークスペースやマシン上にインストールされているスクリプトやツールを実行したりすることはできません。UI 拡張機能の例としては、テーマ、スニペット、言語文法、キーマップなどがあります。

  • ワークスペース拡張機能: これらの拡張機能は、ワークスペースが存在するマシンと同じマシン上で実行されます。ローカルワークスペースでは、ワークスペース拡張機能はローカルマシンで実行されます。リモートワークスペースや Codespaces を使用している場合、ワークスペース拡張機能はリモートマシンや環境で実行されます。ワークスペース拡張機能はワークスペース内のファイルにアクセスして、リッチでマルチファイルな言語サービスやデバッガーのサポートを提供したり、ワークスペース内の複数のファイルに対して(直接、またはスクリプト/ツールを呼び出すことによって)複雑な操作を実行したりできます。ワークスペース拡張機能は UI の変更に重点を置いていませんが、エクスプローラー、ビュー、その他の UI 要素に寄与することも可能です。

ユーザーが拡張機能をインストールすると、VS Code はその種類に基づいて自動的に適切な場所にインストールします。どちらの種類の拡張機能としても実行可能な場合、VS Code は状況に応じて最適なものを選択しようとします。UI 拡張機能は VS Code のローカル拡張機能ホストで実行され、ワークスペース拡張機能は、リモートワークスペースであれば小さな VS Code Server 内にあるリモート拡張機能ホストで実行されます(ローカルの場合はローカル拡張機能ホストで実行されます)。最新の VS Code クライアント機能を利用できるようにするため、サーバーは VS Code クライアントのバージョンと正確に一致させる必要があります。そのため、コンテナ内のフォルダを開いたり、リモート SSH ホストに接続したり、Codespaces や Windows Subsystem for Linux (WSL) を使用したりする際に、Remote Development または GitHub Codespaces 拡張機能によってサーバーが自動的にインストール(または更新)されます。(VS Code はサーバーの開始と停止も自動的に管理するため、ユーザーがその存在を意識する必要はありません。)

Architecture diagram

VS Code API は、UI 拡張機能とワークスペース拡張機能の両方から呼び出された際に、自動的に正しいマシン(ローカルまたはリモート)で実行されるように設計されています。しかし、Node API の使用やシェルスクリプトの実行など、VS Code が提供していない API を拡張機能が使用している場合、リモートで実行したときに正しく動作しない可能性があります。拡張機能のすべての機能が、ローカルとリモートのワークスペースの両方で正しく動作することをテストすることを強く推奨します。

拡張機能のデバッグ

リモート環境でのテスト用に拡張機能の開発版をインストールすることは可能ですが、問題が発生した場合は、リモート環境で直接拡張機能をデバッグしたくなるでしょう。このセクションでは、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 ターミナルウィンドウ(⌃⇧` (Windows, Linux Ctrl+Shift+`))で(yarn installsudo apt-get などを使用して)他の必要な依存関係をインストールできます。

  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 Container または Codespaces: Reopen in Container を実行すると、VS Code はすぐにコンテナをセットアップして接続します。これで、ローカルの場合と同じようにコンテナ内からソースコードを開発できるようになります。

  5. 新しい VS Code ターミナルウィンドウ(⌃⇧` (Windows, Linux Ctrl+Shift+`))で yarn install または npm install を実行し、Linux バージョンの Node.js ネイティブ依存関係がインストールされていることを確認します。他の OS や実行時の依存関係をインストールすることもできますが、コンテナをリビルドした際にも利用できるように、これらを .devcontainer/Dockerfile に追加することをお勧めします。

  6. 最後に、F5 を押すか、実行とデバッグビューを使用して同じコンテナ内で拡張機能を起動し、デバッガーをアタッチします。

    注: 表示されるウィンドウで拡張機能のソースコードフォルダを直接開くことはできませんが、サブフォルダやコンテナ内の他の場所を開くことは可能です。

表示される拡張機能開発ホストウィンドウには、ステップ 2 で定義したコンテナ内で実行され、デバッガーがアタッチされた状態の拡張機能が含まれます。

SSH を使用したデバッグ

手順に従ってください

  1. Remote - SSH 拡張機能をインストールして構成した後、VS Code のコマンドパレット(F1)から Remote-SSH: Connect to Host... を選択してホストに接続します。

  2. 接続後、ファイル > 開く... / フォルダを開く... を使用して拡張機能のソースコードがあるリモートフォルダを選択するか、コマンドパレット(F1)から Git: Clone を選択してリポジトリをクローンし、リモートホスト上で開きます。

  3. 不足している必要な依存関係がある場合は、新しい VS Code ターミナルウィンドウ(⌃⇧` (Windows, Linux Ctrl+Shift+`))でインストールします(例: yarn installapt-get を使用)。

  4. 最後に、F5 を押すか、実行とデバッグビューを使用してリモートホスト上で拡張機能を起動し、デバッガーをアタッチします。

    注: 表示されるウィンドウで拡張機能のソースコードフォルダを直接開くことはできませんが、サブフォルダや SSH ホスト上の他の場所を開くことは可能です。

表示される拡張機能開発ホストウィンドウには、SSH ホスト上で実行され、デバッガーがアタッチされた状態の拡張機能が含まれます。

WSL を使用したデバッグ

次の手順を実行します。

  1. WSL 拡張機能をインストールして構成した後、VS Code のコマンドパレット(F1)から WSL: New Window を選択します。

  2. 表示される新しいウィンドウで、ファイル > 開く... / フォルダを開く... を使用して拡張機能のソースコードがあるリモートフォルダを選択するか、コマンドパレット(F1)から Git: Clone を選択してリポジトリをクローンし、WSL 上で開きます。

    ヒント: /mnt/c フォルダを選択すると、Windows 側にクローンしたソースコードにアクセスできます。

  3. 不足している必要な依存関係がある場合は、新しい VS Code ターミナルウィンドウ(⌃⇧` (Windows, Linux Ctrl+Shift+`))でインストールします(例: apt-get を使用)。少なくとも yarn install または npm install を実行して、ネイティブ Node.js 依存関係の Linux バージョンが利用可能であることを確認する必要があります。

  4. 最後に、F5 を押すか、実行とデバッグビューを使用してローカルと同じように拡張機能を起動し、デバッガーをアタッチします。

    注: 表示されるウィンドウで拡張機能のソースコードフォルダを直接開くことはできませんが、サブフォルダや WSL 内の他の場所を開くことは可能です。

表示される拡張機能開発ホストウィンドウには、WSL 内で実行され、デバッガーがアタッチされた状態の拡張機能が含まれます。

拡張機能の開発版をインストールする

SSH ホスト、コンテナ内、WSL、または GitHub Codespaces を通じて VS Code が自動的に拡張機能をインストールする際は、Marketplace のバージョンが使用されます(ローカルマシンに既にインストールされているバージョンではありません)。

これはほとんどの状況では合理的ですが、デバッグ環境をセットアップすることなく、公開されていないバージョンの拡張機能をテスト(または共有)したい場合があります。公開されていないバージョンの拡張機能をインストールするには、拡張機能を VSIX としてパッケージ化し、既に実行中のリモート環境に接続されている VS Code ウィンドウに手動でインストールします。

次の手順を実行します。

  1. 既に公開されている拡張機能の場合、settings.json"extensions.autoUpdate": false を追加して、Marketplace の最新バージョンに自動更新されないようにすることをお勧めします。
  2. 次に、vsce package を使用して拡張機能を VSIX としてパッケージ化します。
  3. codespaceDev ContainersSSH ホスト、または WSL 環境に接続します。
  4. 拡張機能ビューのその他の操作...)メニューにある Install from VSIX... コマンドを使用して、この特定のウィンドウ(ローカルウィンドウではありません)に拡張機能をインストールします。
  5. プロンプトが表示されたらリロードします。

ヒント: インストール後、Developer: Show Running Extensions コマンドを使用して、VS Code がその拡張機能をローカルで実行しているかリモートで実行しているかを確認できます。

リモート拡張機能における依存関係の処理

拡張機能は、API のために他の拡張機能に依存関係を持たせることができます。例:

  • 拡張機能は、自身の activate 関数から API をエクスポートできます。
  • この API は、同じ拡張機能ホスト内で実行されているすべての拡張機能から利用可能になります。
  • コンシューマー拡張機能は、package.json 内で extensionDependencies プロパティを使用して、提供元の拡張機能に依存していることを宣言します。

すべての拡張機能がローカルで実行され、同じ拡張機能ホストを共有している場合、拡張機能の依存関係は問題なく動作します。

リモートシナリオでは、リモートで実行されている拡張機能がローカルで実行されている拡張機能に依存関係を持つ可能性があります。たとえば、ローカル拡張機能がリモート拡張機能の機能に不可欠なコマンドを公開している場合などです。この場合、リモート拡張機能がローカル拡張機能を extensionDependency として宣言することを推奨しますが、問題は、拡張機能が 2 つの異なる拡張機能ホスト上で実行されることです。つまり、提供元の API をコンシューマーが利用できません。そのため、提供元の拡張機能は、package.json"api": "none" を指定して、API をエクスポートする機能を完全に放棄する必要があります。ただし、拡張機能同士は VS Code コマンド(非同期)を使用して通信することは可能です。

これは提供元の拡張機能に対して不必要に厳しい制約のように思えるかもしれませんが、"api": "none" を使用する拡張機能は、activate メソッドから API を返す機能を放棄するだけです。他の拡張機能ホストで実行されるコンシューマー拡張機能は、引き続き依存関係を持つことができ、アクティベートされます。

一般的な問題

VS Code の API は、拡張機能の場所に関係なく自動的に正しい場所で実行されるように設計されています。これを念頭に置いて、予期しない動作を回避するために役立ついくつかの API を紹介します。

実行場所の誤り

拡張機能が期待通りに機能しない場合、誤った場所で実行されている可能性があります。最も一般的なのは、ローカルのみで実行されることを期待している拡張機能がリモートで実行されているケースです。コマンドパレット(F1)から Developer: Show Running Extensions コマンドを使用して、拡張機能がどこで実行されているかを確認できます。

Developer: Show Running Extensions コマンドで、UI 拡張機能が誤ってワークスペース拡張機能として扱われている(またはその逆)ことが判明した場合は、拡張機能の種類セクションで説明されているように、拡張機能の package.jsonextensionKind プロパティを設定してみてください。

remote.extensionKind 設定を使用すると、拡張機能の種類を変更した際の影響をすぐにテストできます。この設定は、拡張機能 ID と拡張機能の種類のマップです。たとえば、Azure Databases 拡張機能を(デフォルトの Workspace ではなく)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.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);
}

クリップボードの使用

従来、拡張機能の開発者は 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 のようなモジュールを使用してブラウザや他のアプリケーションを起動したりすることは、ローカルシナリオではうまく機能しますが、ワークスペース拡張機能はリモートで実行されるため、アプリケーションが誤った側で起動する可能性があります。VS Code Remote Development は opn ノードモジュールを部分的にシム(shim)し、既存の拡張機能が機能するようにしています。モジュールを URI で呼び出すと、VS Code はその URI のデフォルトアプリケーションをクライアント側で表示します。ただし、これは完全な実装ではなく、オプションはサポートされておらず、child_process オブジェクトも返されません。

サードパーティのノードモジュールに頼る代わりに、vscode.env.openExternal メソッドを使用して、指定された URI に対してローカル OS のデフォルト登録アプリケーションを起動することを推奨します。さらに、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.dokyumento.jp'));

      // 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 を全く参照していない可能性があることに注意してください。そのため、URI をそのまま完全な形で使用する必要があります。これは localhost が使用できない Codespaces ブラウザベースエディタにおいて特に重要です。

コールバックと URI ハンドラー

vscode.window.registerUriHandler API を使用すると、拡張機能がカスタム URI を登録できます。この URI がブラウザで開かれると、拡張機能内のコールバック関数が起動します。URI ハンドラーを登録する一般的なユースケースは、OAuth 2.0 認証プロバイダー(例: Azure AD)を使用したサービスサインインを実装する場合です。ただし、外部アプリケーションやブラウザから拡張機能に情報を送信したいあらゆるシナリオで使用可能です。

VS Code の Remote Development および 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 Authorization Code with PKCE flow(例: Azure AD は PKCE をサポート)を使用することを推奨します。

リモートまたは Codespaces ブラウザエディタでの実行時の動作の違い

ワークスペース拡張機能は、リモートで実行する際や Codespaces ブラウザベースエディタで実行する際に、動作を変える必要がある場合があります。VS Code はこれらの状況を検出するために vscode.env.uiKindextension.extensionKindvscode.env.remoteName という 3 つの API を提供しています。

次に、これら 3 つの 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>`;

動的な Webview コンテンツにはメッセージパッシング API を使用する

VS Code の Webview には、ローカル Web サーバーを使用せずに Webview コンテンツを動的に更新できる メッセージパッシング API が含まれています。Webview コンテンツを更新するためにやり取りしたいローカル Web サービスを拡張機能が実行している場合でも、HTML コンテンツから直接ではなく、拡張機能自体からこれを行うことができます。

これは、Remote Development と GitHub Codespaces において、Webview コードが VS Code と Codespaces ブラウザベースエディタの両方で動作するようにするための重要なパターンです。

なぜ localhost Web サーバーではなくメッセージパッシングなのか?

代替パターンは、iframe で Web コンテンツを提供するか、Webview コンテンツが localhost サーバーと直接対話することです。残念ながら、デフォルトでは Webview 内の localhost は開発者のローカルマシンに解決されます。つまり、リモートで実行されているワークスペース拡張機能にとって、それが作成する Webview は、拡張機能が生成したローカルサーバーにアクセスできません。マシンの IP を使用した場合でも、接続先のポートはクラウド VM やコンテナでは通常デフォルトでブロックされます。これが VS Code で動作したとしても、Codespaces ブラウザベースエディタでは動作しません。

以下は Remote - SSH 拡張機能を使用した場合の問題の図ですが、この問題は Dev Containers や GitHub Codespaces でも同様に発生します。

Webview problem

可能であれば、これを行うことは避けてください。拡張機能が非常に複雑になるためです。メッセージパッシング API は、このような頭痛の種なしに同じユーザー体験を実現できます。拡張機能自体がリモート側の VS Code Server 内で実行されるため、Webview から渡されたメッセージの結果として拡張機能が起動する Web サーバーと透過的に対話できます。

Webview から localhost を使用する場合の回避策

何らかの理由で メッセージパッシング API が使用できない場合、VS Code の Remote Development および GitHub Codespaces 拡張機能で動作するオプションが 2 つあります。

各オプションは、Webview コンテンツが VS Code が VS Code Server と通信するために使用する同じチャネルを経由することを可能にします。たとえば、前述の Remote - SSH の図を更新すると、次のようになります。

Webview Solution

オプション 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 バージョンを実行しており、これが原因でリモート使用時にバイナリが失敗する可能性があります。

この問題を解決するには

  1. VS Code が提供する Node.js の「モジュール」バージョンに対して、両方のバイナリセット(Electron と標準 Node.js)を含める(または動的に取得する)ようにします。
  2. vscode.extensions.getExtension('your.extensionId').extensionKind === vscode.ExtensionKind.Workspace をチェックして、拡張機能がリモートまたはローカルのどちらで実行されているかに基づいて正しいバイナリを設定します。
  3. 同様のロジックに従うことで、同時に非 x86_64 ターゲットや Alpine Linux のサポートを追加することもできます。

VS Code が使用している「モジュール」バージョンを確認するには、ヘルプ > 開発者ツール を開いてコンソールに process.versions.modules と入力します。ただし、ネイティブモジュールが異なる 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 固有のネイティブコードやランタイムが含まれており、それらのアーキテクチャ/OS で失敗している可能性があります。

たとえば、拡張機能が x86_64 でコンパイルされたバージョンのネイティブモジュールやランタイムのみを含んでいる場合があります。Alpine Linux の場合、Alpine Linux の libcmusl)の実装と他のディストリビューション(glibc)との根本的な違いにより、ネイティブコードやランタイムが動作しないことがあります。

この問題を解決するには

  1. コンパイルされたコードを動的に取得している場合は、process.arch を使用して非 x86_64 ターゲットを検出し、正しいアーキテクチャ用にコンパイルされたバージョンをダウンロードすることでサポートを追加できます。サポートされているすべてのアーキテクチャ用のバイナリを拡張機能内に含めている場合は、同じロジックを使用して正しいものを使用できます。

  2. Alpine Linux の場合、await fs.exists('/etc/alpine-release') を使用して OS を検出し、同様に musl ベースの OS に適したバイナリをダウンロードまたは使用できます。

  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 リポジトリのイシューで追跡されています。

質問とフィードバック

© . This site is unofficial and not affiliated with Microsoft.