VS Codeのエージェントモードを拡張するには、を試してください!

リモート開発と GitHub Codespaces のサポート

Visual Studio Code Remote Development を使用すると、他のマシン (仮想または物理) にあるソース コードやランタイム環境と透過的にやり取りできます。GitHub Codespaces は、VS Code とブラウザベースのエディタの両方からアクセスできる、マネージド クラウド ホスト環境でこれらの機能を拡張するサービスです。

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

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

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

リモート開発や 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 クライアント機能が利用できるように、サーバーは VS Code クライアントのバージョンと正確に一致する必要があります。したがって、コンテナー、リモート SSH ホスト、Codespaces、または Windows Subsystem for Linux (WSL) でフォルダーを開くと、リモート開発または GitHub Codespaces 拡張機能によってサーバーが自動的にインストール (または更新) されます。(VS Code はサーバーの起動と停止も自動的に管理するため、ユーザーはサーバーの存在を意識しません)。

Architecture diagram

VS Code API は、UI 拡張機能とワークスペース拡張機能の両方から呼び出されたときに、正しいマシン (ローカルまたはリモート) で自動的に実行されるように設計されています。ただし、拡張機能が VS Code が提供しない API (Node 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: Add Dev Container Configuration Files... を実行すると、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 を使用) をインストールします。Node.js ネイティブ依存関係の Linux バージョンが利用可能であることを確認するために、少なくとも yarn install または npm install を実行する必要があります。

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

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

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

拡張機能の開発バージョンのインストール

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

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

次の手順に従ってください

  1. 公開済みの拡張機能である場合は、自動的に最新の Marketplace バージョンに更新されないように、settings.json"extensions.autoUpdate": false を追加することをお勧めします。
  2. 次に、vsce package を使用して拡張機能を VSIX としてパッケージ化します。
  3. codespace開発コンテナーSSH ホスト、またはWSL 環境に接続します。
  4. 拡張機能ビューのその他のアクション (...) メニューにあるVSIX からインストール... コマンドを使用して、この特定のウィンドウ (ローカルではない) に拡張機能をインストールします。
  5. プロンプトが表示されたら再読み込みします。

ヒント: インストール後、開発者: 実行中の拡張機能を表示 コマンドを使用して、VS Code が拡張機能をローカルで実行しているかリモートで実行しているかを確認できます。

リモート拡張機能の依存関係の処理

拡張機能は、API のために他の拡張機能に依存する可能性があります。例えば

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

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

リモート シナリオを扱う場合、リモートで実行されている拡張機能が、ローカルで実行されている拡張機能に拡張機能の依存関係を持つ可能性があります。たとえば、ローカル拡張機能がリモート拡張機能の機能にとって重要なコマンドを公開している場合などです。この場合、リモート拡張機能はローカル拡張機能を extensionDependency として宣言することをお勧めしますが、問題は拡張機能が 2 つの異なる拡張機能ホストで実行されるため、プロバイダーからの API がコンシューマーに利用できないことです。したがって、提供する拡張機能は、拡張機能の package.json"api": "none" を使用して、API をエクスポートする機能を完全に放棄する必要があります。拡張機能は VS Code コマンド (非同期) を使用して引き続き通信できます。

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

一般的な問題

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

不正確な実行場所

拡張機能が期待どおりに機能しない場合、間違った場所で実行されている可能性があります。最も一般的なのは、ローカルでしか実行されないと期待しているのに、リモートで実行されていることです。コマンド パレット (F1) から開発者: 実行中の拡張機能を表示コマンドを使用して、拡張機能がどこで実行されているかを確認できます。

開発者: 実行中の拡張機能を表示コマンドで、UI 拡張機能がワークスペース拡張機能として誤って扱われているか、またはその逆であることが示された場合、拡張機能の種類セクションで説明されているように、拡張機能のpackage.jsonextensionKindプロパティを設定してみてください。

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 を使用して、その状態を設定同期に提供します。これにより、複数のマシンでユーザーに同じウェルカム ページや更新ページを表示するのを防ぐことができます。

拡張機能の機能のトピックには、setKeysforSync の使用例があります。

秘密情報の永続化

拡張機能がパスワードやその他の秘密情報を永続化する必要がある場合、暗号化によって保護されたファイルシステムに安全にテキストを保存する方法を提供する Visual Studio Code のSecretStorage API の使用を検討することをお勧めします。たとえば、デスクトップでは、Electron のsafeStorage API を使用して、ファイルシステムに保存する前に秘密情報を暗号化しています。この API は常にクライアント側に秘密情報を保存しますが、拡張機能がどこで実行されていてもこの API を使用して同じ秘密情報の値を取得できます。

: この API は、パスワードと秘密情報を永続化するための推奨される方法です。これらの API はデータを平文で保存するため、vscode.ExtensionContext.workspaceState または vscode.ExtensionContext.globalState を使用して秘密情報を保存してはなりません。

以下に例を示します

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 Remote Developmentは、既存の拡張機能が機能するように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.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.openExternallocalhost 転送メカニズムは便利ですが、実際に新しいブラウザー ウィンドウやアプリケーションを起動せずに何かを転送したい状況もあるかもしれません。ここで 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 をまったく参照しない可能性があることに注意することが重要です。したがって、全体として使用する必要があります。これは、localhost を使用できない Codespaces ブラウザーベースのエディターにとって特に重要です。

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

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

VS Code のリモート開発および Codespaces 拡張機能は、拡張機能が実際に実行されている場所 (ローカルまたはリモート) に関係なく、URI を拡張機能に透過的に渡します。ただし、vscode:// URI は Codespaces ブラウザベースのエディターでは機能しません。これらの URI をブラウザなどで開くと、ブラウザベースのエディターではなくローカルの VS Code クライアントに渡そうとするためです。幸いなことに、これは vscode.env.asExternalUri API を使用することで簡単に解決できます。

vscode.window.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 フローを使用することをお勧めします (例: Azure AD は PKCE をサポートしています)。

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

場合によっては、ワークスペース拡張機能がリモートで実行されるときに動作を変更する必要があるかもしれません。また、Codespaces ブラウザーベースのエディターで実行されるときに動作を変更したい場合もあるかもしれません。VS Code は、これらの状況を検出するための 3 つの API を提供します: vscode.env.uiKindextension.extensionKind、および vscode.env.remoteName

次に、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 を介して) を返します。これらの API は、関連するすべての拡張機能が同じ側 (すべて 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 コンテンツから直接ではなく、拡張機能自体からこれを行うことができます。

これは、リモート開発と 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 リクエストをプログラムでリモートに転送できるようにします。この同じ API を使用して、拡張機能が VS Code で実行されているときに 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 固有のネイティブ コードまたはランタイムが含まれており、これらのアーキテクチャ/オペレーティング システムでは失敗する可能性があります。

たとえば、拡張機能には 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');

このような状況は可能な限り避けるようにしてください。

既知の問題

ワークスペース拡張機能に機能を追加することで解決できる拡張機能の問題がいくつかあります。以下の表は、検討中の既知の問題のリストです。

問題 説明
ワークスペース拡張機能からアタッチされたデバイスにアクセスできない ローカルに接続されたデバイスにアクセスする拡張機能は、リモートで実行されるとそれらに接続できません。これを克服する1つのアプローチは、接続されたデバイスにアクセスし、リモート拡張機能が呼び出すことができるコマンドを提供するコンパニオンUI拡張機能を作成することです。
別の方法はリバース トンネリングで、これはVS Code リポジトリの Issue で追跡されています。

質問とフィードバック