に参加して、VS Code の AI 支援開発について学びましょう。

リモート開発と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クライアント機能が利用できるように、サーバーはVS Codeクライアントバージョンと完全に一致する必要があります。そのため、コンテナ、リモートSSHホスト、Codespaces、またはWSL(Windows Subsystem for Linux)でフォルダを開くと、Remote Developmentまたは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: Dev Container構成ファイルを追加... または Codespaces: Dev Container構成ファイルを追加... を選択し、必要なコンテナ構成ファイルを追加するためにNode.js & TypeScript(TypeScriptを使用しない場合はNode.js)を選択します。

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

  4. Dev Containers: コンテナで再度開く または Codespaces: Dev 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: ホストに接続... を選択してホストに接続します。

  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. codespaceDev ContainersSSHホスト、または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: 構成ファイルの編集拡張機能をワークスペース拡張機能(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を使用することをお勧めします。この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?`
      );
    })
  );
}

ローカルブラウザまたはアプリケーションで何かを開く

特定のURIに対してブラウザまたは他のアプリケーションを起動するためにプロセスを生成したり、opnのようなモジュールを使用したりすることは、ローカルシナリオではうまく機能しますが、ワークスペース拡張機能はリモートで実行されるため、アプリケーションが誤った側で起動する可能性があります。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を全く参照しない場合があることに注意することが重要です。したがって、URI全体を使用する必要があります。これは、localhostを使用できないCodespacesのブラウザベースのエディタにとって特に重要です。

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

vscode.window.registerUriHandler APIを使用すると、拡張機能がカスタム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経由のコールバックにワイルドカードホスト名を許可しないプロバイダーもあるためです。また、コールバックのセキュリティを向上させるために、可能な限りPKCEフロー付きのOAuth 2.0承認コードを使用することをお勧めします(例: 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.');
  }
}

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

一部の拡張機能は、アクティベーションの一部として、他の拡張機能(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にはメッセージパッシングAPIが含まれており、ローカルWebサーバーを使用せずにWebviewコンテンツを動的に更新できます。たとえ拡張機能がWebviewコンテンツを更新するために連携したいローカルWebサービスを実行している場合でも、HTMLコンテンツから直接ではなく、拡張機能自体からこれを行うことができます。

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

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

もう一つのパターンは、iframeでウェブコンテンツを提供するか、ウェブビューコンテンツがlocalhostサーバーと直接やり取りすることです。残念ながら、デフォルトではウェブビュー内のlocalhostは開発者のローカルマシンに解決されます。これは、リモートで実行されているワークスペース拡張機能の場合、それが作成するウェブビューは拡張機能によって起動されたローカルサーバーにアクセスできないことを意味します。たとえこれがVS Codeで機能したとしても、クラウドVMやコンテナでは接続しようとしているポートがデフォルトでブロックされることが多く、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では、拡張機能がローカルの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の場合、libcがAlpine Linux (musl)と他のディストリビューション (glibc)でどのように実装されているかの根本的な違いにより、含まれるネイティブコードまたはランタイムが動作しない場合があります。

この問題を解決するには

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

  2. Alpine Linuxの場合、await fs.exists('/etc/alpine-release')を使用してオペレーティングシステムを検出し、再度、muslベースのオペレーティングシステム用の正しいバイナリをダウンロードまたは使用できます。

  3. これらのプラットフォームをサポートしない場合は、同じロジックを使用して代わりに適切なエラーメッセージを提供できます。

一部のサードパーティのnpmモジュールには、この問題を引き起こす可能性のあるネイティブコードが含まれていることに注意することが重要です。したがって、場合によっては、npmモジュールの作成者と協力して追加のコンパイルターゲットを追加する必要があるかもしれません。

Electronモジュールの使用を避ける

拡張機能APIによって公開されていない組み込みのElectronまたはVS Codeモジュールに依存することは便利ですが、VS Code Serverは標準の(Electronではない)Node.jsバージョンを実行することに注意することが重要です。これらのモジュールはリモートで実行すると見つかりません。ただし、それらを機能させるための特定のコードがあるいくつかの例外があります。

これらの問題を回避するには、基本的なNode.jsモジュールまたは拡張機能VSIX内のモジュールを使用してください。Electronモジュールをどうしても使用する必要がある場合は、モジュールが見つからない場合のフォールバックを必ず用意してください。

以下の例では、Electronのoriginal-fsノードモジュールが見つかればそれを使用し、見つからなければ基本的なNode.jsのfsモジュールにフォールバックします。

function requireWithFallback(electronModule: string, nodeModule: string) {
  try {
    return require(electronModule);
  } catch (err) {}
  return require(nodeModule);
}

const fs = requireWithFallback('original-fs', 'fs');

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

既知の問題

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

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

質問とフィードバック

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