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で開きます。必要に応じて、codespaceをVS Codeで開くこともできます。

  2. GitHub Codespacesのデフォルトイメージにはほとんどの拡張機能に必要な前提条件がすべて含まれているはずですが、新しいVS Codeターミナルウィンドウ (⌃⇧` (Windows, Linux Ctrl+Shift+`)) で、その他の必要な依存関係 (たとえば、`yarn install` や `sudo apt-get` を使用して) をインストールできます。

  3. 最後に、F5を押すか、実行とデバッグビューを使用してcodespace内で拡張機能を起動します。

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

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

カスタム開発コンテナでのデバッグ

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

  1. ローカルで開発コンテナを使用するには、Dev Containers拡張機能をインストールして構成しファイル > 開く... / フォルダーを開く... を使用してソースコードをVS Codeでローカルに開きます。代わりにCodespacesを使用するには、GitHub上の拡張機能を含むリポジトリに移動し、ブラウザベースのエディターで作業するためにcodespaceで開きます。必要に応じて、codespaceをVS Codeで開くこともできます。

  2. コマンドパレット (F1) から **Dev Containers: 開発コンテナ構成ファイルを追加...** または **Codespaces: 開発コンテナ構成ファイルを追加...** を選択し、必要なコンテナ構成ファイルを追加するために **Node.js & TypeScript** (TypeScriptを使用していない場合はNode.js) を選択します。

  3. オプション: このコマンドの実行後、追加のビルドまたはランタイム要件を含めるために、.devcontainer フォルダーの内容を変更できます。詳細については、詳細な開発コンテナの作成ドキュメントを参照してください。

  4. **Dev Containers: コンテナで再開** または **Codespaces: 開発コンテナ構成ファイルを追加...** を実行すると、すぐに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: ホストに接続...** を選択してホストに接続します。

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

  3. 新しいVS Codeターミナルウィンドウ (⌃⇧` (Windows, Linux Ctrl+Shift+`)) で、不足している可能性のある必要な依存関係 (たとえば、`yarn install` や `apt-get` を使用して) をインストールします。

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

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

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

WSLを使用したデバッグ

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

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

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

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

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

  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.json` で `extensionDependencies` プロパティを使用して、提供する拡張機能に依存していることを宣言します。

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

リモートシナリオを扱う場合、リモートで実行されている拡張機能が、ローカルで実行されている拡張機能に依存関係を持つ可能性があります。たとえば、ローカル拡張機能がリモート拡張機能の機能に不可欠なコマンドを公開している場合です。この場合、リモート拡張機能がローカル拡張機能を`extensionDependency`として宣言することをお勧めしますが、問題は拡張機能が2つの異なる拡張機能ホストで実行されるため、プロバイダーからの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 Databases拡張機能をUI拡張機能として強制的に (ワークスペースのデフォルトではなく) 動作させ、Remote - 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`) や特定の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はこの問題を解決します。この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` Nodeモジュールを**部分的に**シムします。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.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のリモート開発およびCodespaces拡張機能は、URIが実際にどこで実行されているか (ローカルかリモートか) にかかわらず、透過的にURIを拡張機能に渡します。ただし、`vscode://` URIはCodespacesのブラウザベースエディターでは機能しません。これらのURIをブラウザのようなもので開くと、ブラウザベースエディターではなくローカルのVS Codeクライアントに渡そうとするためです。幸いなことに、これは `vscode.env.asExternalUri` APIを使用することで簡単に解決できます。

`vscode.window.registerUriHandler` と `vscode.env.asExternalUri` を組み合わせて、OAuth認証コールバックの例を構築してみましょう。

import * as vscode from 'vscode';

// This is ${publisher}.${name} from package.json
const extensionId = 'my.amazing-extension';

export async function activate(context: vscode.ExtensionContext) {
  // Register a URI handler for the authentication callback
  vscode.window.registerUriHandler({
    handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
      // Add your code for what to do when the authentication completes here.
      if (uri.path === '/auth-complete') {
        vscode.window.showInformationMessage('Sign in successful!');
      }
    }
  });

  // Register a sign in command
  context.subscriptions.push(
    vscode.commands.registerCommand(`${extensionId}.signin`, async () => {
      // Get an externally addressable callback URI for the handler that the authentication provider can use
      const callbackUri = await vscode.env.asExternalUri(
        vscode.Uri.parse(`${vscode.env.uriScheme}://${extensionId}/auth-complete`)
      );

      // Add your code to integrate with an authentication provider here - we'll fake it.
      vscode.env.clipboard.writeText(callbackUri.toString());
      await vscode.window.showInformationMessage(
        'Open the URI copied to the clipboard in a browser window to authorize.'
      );
    })
  );
}

VS Codeでこのサンプルを実行すると、認証プロバイダーのコールバックとして使用できる `vscode://` または `vscode-insiders://` URIが接続されます。Codespacesのブラウザベースエディターで実行すると、コードの変更や特別な条件なしに `https://*.github.dev` URIが接続されます。

OAuthはこのドキュメントの範囲外ですが、このサンプルを実際の認証プロバイダーに適合させる場合、プロバイダーの前にプロキシサービスを構築する必要があるかもしれません。これは、すべてのプロバイダーが `vscode://` コールバックURIを許可しているわけではなく、HTTPS経由のコールバックにワイルドカードホスト名を許可していないものもあるためです。また、コールバックのセキュリティを向上させるために、可能な限りPKCEフロー付きOAuth 2.0認証コードを使用することをお勧めします (例: Azure ADはPKCEをサポートしています)。

リモートまたはCodespacesブラウザエディターで実行する際の動作の変更

場合によっては、ワークスペース拡張機能はリモートで実行するときに動作を変更する必要があるかもしれません。また、Codespacesのブラウザベースエディターで実行するときに動作を変更したい場合もあるでしょう。VS Codeは、これらの状況を検出するための3つのAPI、`vscode.env.uiKind`、`extension.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.');
  }
}

コマンドを使用した拡張機能間の通信

一部の拡張機能は、アクティベーションの一部として他の拡張機能 ( `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を使用する必要があります。Codespacesのブラウザベースエディターが拡張機能で動作するようにするには、`vscode-resource://` URIをハードコーディングする代わりにこのAPIを使用することが必須です。詳細については、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コンテンツから直接行うのではなく、拡張機能自体からこれを行うことができます。

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

localhost Webサーバーではなくメッセージパッシングを使う理由

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

以下に、Remote - SSH拡張機能を使用する場合のこの問題の図を示しますが、この問題は開発コンテナとGitHub Codespacesでも存在します。

Webview problem

可能であれば、**これを避けるべきです**。これにより拡張機能が大幅に複雑になるためです。メッセージパッシングAPIを使用すれば、このような煩わしさなしに同じ種類のユーザーエクスペリエンスを実現できます。拡張機能自体はリモート側のVS Code Serverで実行されるため、webviewから渡されるメッセージの結果として拡張機能が起動するWebサーバーと透過的にやり取りできます。

Webviewからlocalhostを使用するための回避策

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

各オプションを使用すると、webviewコンテンツを、VS CodeがVS Code Serverと通信するために使用するのと同じチャネルを介してルーティングできます。たとえば、前のセクションのRemote - SSHの図を更新すると、次のようになります。

Webview Solution

オプション1 - asExternalUri を使用する

VS Code 1.40では、拡張機能がローカルの `http` および `https` リクエストをプログラム的にリモート転送できるようにする `vscode.env.asExternalUri` APIが導入されました。この同じ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拡張機能を作成することです。
もう1つのアプローチはリバーストンネリングであり、これはVS Codeリポジトリの課題で追跡されています。

質問とフィードバック