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

Web拡張機能

Visual Studio Code は、ブラウザ上でエディターとして実行できます。一例として、GitHub でリポジトリやプルリクエストを閲覧中に . (ピリオドキー) を押すと表示される github.dev ユーザーインターフェースがあります。VS Code が Web で使用される場合、インストールされた拡張機能は、ブラウザ内の「Web 拡張機能ホスト」と呼ばれる拡張機能ホストで実行されます。Web 拡張機能ホストで実行できる拡張機能は、「Web 拡張機能」と呼ばれます。

Web 拡張機能は通常の拡張機能と同じ構造を共有しますが、ランタイムが異なるため、Node.js ランタイム用に書かれた拡張機能と同じコードでは実行されません。Web 拡張機能は引き続き VS Code API 全体にアクセスできますが、Node.js API やモジュール読み込みにはアクセスできません。代わりに、Web 拡張機能はブラウザのサンドボックスによって制限され、通常の拡張機能と比較して制限があります。

Web 拡張機能のランタイムは、VS Code デスクトップでもサポートされています。拡張機能を Web 拡張機能として作成することを決定した場合、Web 用 VS Code (vscode.devgithub.dev を含む) はもちろん、デスクトップや GitHub Codespaces などのサービスでもサポートされます。

Web 拡張機能の構造

Web 拡張機能は、通常の拡張機能と同様の構造になっています。拡張機能マニフェスト (package.json) は、拡張機能のソースコードのエントリファイルを定義し、拡張機能の寄与を宣言します。

Web 拡張機能の場合、メインエントリファイルは、通常の拡張機能のように main プロパティではなく、browser プロパティで定義されます。

contributes プロパティは、Web 拡張機能と通常の拡張機能の両方で同じように機能します。

以下の例は、Web 拡張機能ホストでのみ実行されるシンプルなハローワールド拡張機能の package.json を示しています (browser エントリポイントのみを持っています)。

{
  "name": "helloworld-web-sample",
  "displayName": "helloworld-web-sample",
  "description": "HelloWorld example for VS Code in the browser",
  "version": "0.0.1",
  "publisher": "vscode-samples",
  "repository": "https://github.com/microsoft/vscode-extension-samples/helloworld-web-sample",
  "engines": {
    "vscode": "^1.74.0"
  },
  "categories": ["Other"],
  "activationEvents": [],
  "browser": "./dist/web/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "helloworld-web-sample.helloWorld",
        "title": "Hello World"
      }
    ]
  },
  "scripts": {
    "vscode:prepublish": "npm run package-web",
    "compile-web": "webpack",
    "watch-web": "webpack --watch",
    "package-web": "webpack --mode production --devtool hidden-source-map"
  },
  "devDependencies": {
    "@types/vscode": "^1.59.0",
    "ts-loader": "^9.2.2",
    "webpack": "^5.38.1",
    "webpack-cli": "^4.7.0",
    "@types/webpack-env": "^1.16.0",
    "process": "^0.11.10"
  }
}

注意: 拡張機能が 1.74 より前の VS Code バージョンを対象とする場合、activationEventsonCommand:helloworld-web-sample.helloWorld を明示的にリストする必要があります。

browser を持たず main エントリポイントのみを持つ拡張機能は、Web 拡張機能ではありません。それらは Web 拡張機能ホストによって無視され、拡張機能ビューでダウンロードできません。

Extensions view

宣言的な寄与のみを持つ拡張機能 (contributes のみで、main または browser を持たない拡張機能) は Web 拡張機能となり得ます。これらは、拡張機能の作成者による変更なしに、Web 版 VS Code にインストールして実行できます。宣言的な寄与を持つ拡張機能の例には、テーマ、文法、スニペットなどがあります。

拡張機能は、ブラウザと Node.js ランタイムの両方で実行するために、browsermain の両方のエントリポイントを持つことができます。既存の拡張機能を Web 拡張機能に更新する セクションでは、両方のランタイムで動作するように拡張機能を移行する方法を示しています。

Web 拡張機能の有効化セクションには、拡張機能が Web 拡張機能ホストに読み込まれるかどうかを決定するために使用されるルールがリストされています。

Web 拡張機能のメインファイル

Web 拡張機能のメインファイルは、browser プロパティで定義されます。スクリプトは、ブラウザ WebWorker 環境の Web 拡張機能ホストで実行されます。これは、ブラウザワーカーのサンドボックスによって制限され、Node.js ランタイムで実行される通常の拡張機能と比較して制限があります。

  • 他のモジュールのインポートまたは要求はサポートされていません。importScripts も利用できません。結果として、コードは単一のファイルにパッケージ化する必要があります。
  • VS Code APIrequire('vscode') パターンを介してロードできます。これは require のシムがあるため機能しますが、このシムは追加の拡張機能ファイルや追加のノードモジュールをロードするためには使用できません。これは require('vscode') のみで機能します。
  • processossetImmediatepathutilurl などの Node.js グローバルおよびライブラリは、ランタイムでは利用できません。ただし、webpack などのツールで追加できます。webpack 構成セクションで、これを行う方法を説明しています。
  • 開かれたワークスペースまたはフォルダーは仮想ファイルシステム上にあります。ワークスペースファイルへのアクセスは、vscode.workspace.fs でアクセスできる VS Code ファイルシステム API を介して行う必要があります。
  • 拡張機能コンテキストの場所 (ExtensionContext.extensionUri) およびストレージの場所 (ExtensionContext.storageUriglobalStorageUri) も仮想ファイルシステム上にあり、vscode.workspace.fs を介してアクセスする必要があります。
  • Web リソースにアクセスするには、Fetch API を使用する必要があります。アクセスされるリソースは、クロスオリジンリソース共有 (CORS) をサポートしている必要があります。
  • 子プロセスを作成したり、実行可能ファイルを実行したりすることはできません。ただし、Worker API を介して Web ワーカーを作成できます。これは、Web 拡張機能における言語サーバープロトコルセクションで説明されているように、言語サーバーを実行するために使用されます。
  • 通常の拡張機能と同様に、拡張機能の activate/deactivate 関数は exports.activate = ... パターンでエクスポートする必要があります。

Web 拡張機能の開発

幸いなことに、TypeScript や webpack のようなツールは、ブラウザのランタイム制約の多くを隠し、通常の拡張機能と同じ方法で Web 拡張機能を記述できるようにします。Web 拡張機能と通常の拡張機能の両方を、多くの場合、同じソースコードから生成できます。

例えば、yo code ジェネレーターによって作成される Hello Web Extension は、ビルドスクリプトのみが異なります。デバッグ: デバッグの選択と開始 コマンドを使用してアクセスできる、提供された起動設定を使用して、生成された拡張機能を従来の Node.js 拡張機能と同じように実行およびデバッグできます。

Web 拡張機能を作成する

新しい Web 拡張機能をスケルトン化するには、yo code を使用し、New Web Extension を選択します。generator-code の最新バージョン (>= generator-code@1.6) がインストールされていることを確認してください。ジェネレーターと yo を更新するには、npm i -g yo generator-code を実行します。

作成される拡張機能は、拡張機能のソースコード (ハローワールド通知を表示するコマンド)、package.json マニフェストファイル、および webpack または esbuild の構成ファイルで構成されます。

物事をよりシンプルにするために、バンドラーとして webpack を使用すると仮定します。記事の最後に、esbuild を選択した場合の違いについても説明します。

  • src/web/extension.ts は拡張機能のエントリソースコードファイルです。これは通常の hello 拡張機能と同一です。
  • package.json は拡張機能マニフェストです。
    • browser プロパティを使用してエントリファイルを指します。
    • コンパイル、監視、パッケージ化のためのスクリプト: compile-webwatch-webpackage-web を提供します。
  • webpack.config.js は、拡張機能のソースを単一のファイルにコンパイルおよびバンドルする webpack 構成ファイルです。
  • .vscode/launch.json には、Web 拡張機能ホスト (extensions.webWorker 設定は不要になりました) を使用して VS Code デスクトップで Web 拡張機能とテストを実行する起動設定が含まれています。
  • .vscode/task.json には、起動設定で使用されるビルドタスクが含まれています。これは npm run watch-web を使用し、webpack 固有の ts-webpack-watch 問題マッチャーに依存しています。
  • .vscode/extensions.json には、問題マッチャーを提供する拡張機能が含まれています。これらの拡張機能は、起動設定を機能させるためにインストールする必要があります。
  • tsconfig.json は、webworker ランタイムに一致するコンパイルオプションを定義します。

helloworld-web-sample のソースコードは、ジェネレーターによって作成されるものと似ています。

Webpack の構成

webpack 構成ファイルは yo code によって自動的に生成されます。これにより、拡張機能のソースコードが単一の JavaScript ファイルにバンドルされ、Web 拡張機能ホストに読み込まれます。

後で、バンドラーとして esbuild を使用する方法を説明しますが、ここではまず webpack から始めます。

webpack.config.js

const path = require('path');
const webpack = require('webpack');

/** @typedef {import('webpack').Configuration} WebpackConfig **/
/** @type WebpackConfig */
const webExtensionConfig = {
  mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
  target: 'webworker', // extensions run in a webworker context
  entry: {
    extension: './src/web/extension.ts', // source of the web extension main file
    'test/suite/index': './src/web/test/suite/index.ts' // source of the web extension test runner
  },
  output: {
    filename: '[name].js',
    path: path.join(__dirname, './dist/web'),
    libraryTarget: 'commonjs',
    devtoolModuleFilenameTemplate: '../../[resource-path]'
  },
  resolve: {
    mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules
    extensions: ['.ts', '.js'], // support ts-files and js-files
    alias: {
      // provides alternate implementation for node module and source files
    },
    fallback: {
      // Webpack 5 no longer polyfills Node.js core modules automatically.
      // see https://webpack.dokyumento.jp/configuration/resolve/#resolvefallback
      // for the list of Node.js core module polyfills.
      assert: require.resolve('assert')
    }
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'ts-loader'
          }
        ]
      }
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({
      process: 'process/browser' // provide a shim for the global `process` variable
    })
  ],
  externals: {
    vscode: 'commonjs vscode' // ignored because it doesn't exist
  },
  performance: {
    hints: false
  },
  devtool: 'nosources-source-map' // create a source map that points to the original source file
};
module.exports = [webExtensionConfig];

webpack.config.js のいくつかの重要なフィールドは次のとおりです。

  • entry フィールドには、拡張機能とテストスイートのメインエントリポイントが含まれています。
    • このパスを拡張機能のエントリポイントに適切に設定する必要がある場合があります。
    • 既存の拡張機能の場合、このパスを package.jsonmain で現在使用しているファイルを指すことから始めることができます。
    • テストをパッケージ化しない場合は、テストスイートフィールドを省略できます。
  • output フィールドは、コンパイルされたファイルの場所を示します。
    • [name]entry で使用されたキーに置き換えられます。したがって、生成された構成ファイルでは、dist/web/extension.jsdist/web/test/suite/index.js が生成されます。
  • target フィールドは、コンパイルされた JavaScript ファイルが実行される環境の種類を示します。Web 拡張機能の場合、これは webworker である必要があります。
  • resolve フィールドには、ブラウザで動作しない Node ライブラリのエイリアスとフォールバックを追加する機能が含まれています。
    • path のようなライブラリを使用している場合、Web コンパイルされたコンテキストで path をどのように解決するかを指定できます。たとえば、path: path.resolve(__dirname, 'src/my-path-implementation-for-web.js')path を定義するプロジェクト内のファイルを指すことができます。または、Browserify ノードパッケージ版のライブラリ path-browserify を使用し、path: require.resolve('path-browserify') と指定できます。
    • Node.js コアモジュールのポリフィルの一覧については、webpack resolve.fallback を参照してください。
  • plugins セクションでは、DefinePlugin プラグインを使用して、process Node.js グローバルなどのグローバルをポリフィルします。

Web 拡張機能のテスト

現在、マーケットプレイスに公開する前に Web 拡張機能をテストする方法が 3 つあります。

  • デスクトップで実行されている VS Code を使用し、--extensionDevelopmentKind=web オプションを指定して、VS Code で実行されている Web 拡張機能ホストで Web 拡張機能を実行します。
  • @vscode/test-web node モジュールを使用して、ローカルサーバーから提供される、拡張機能を含む Web 用 VS Code を含むブラウザを開きます。
  • vscode.dev に拡張機能をサイドロードして、実際の環境で拡張機能の動作を確認します。

デスクトップで実行されている VS Code で Web 拡張機能をテストする

既存の VS Code 拡張機能開発エクスペリエンスを使用するために、デスクトップで実行されている VS Code は、通常の Node.js 拡張機能ホストとともに Web 拡張機能ホストの実行をサポートしています。

New Web Extension ジェネレーターが提供する pwa-extensionhost 起動設定を使用します。

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Run Web Extension in VS Code",
      "type": "pwa-extensionHost",
      "debugWebWorkerHost": true,
      "request": "launch",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}",
        "--extensionDevelopmentKind=web"
      ],
      "outFiles": ["${workspaceFolder}/dist/web/**/*.js"],
      "preLaunchTask": "npm: watch-web"
    }
  ]
}

npm run watch-web を呼び出して拡張機能をコンパイルするために、タスク npm: watch-web を使用します。このタスクは tasks.json にあることが想定されています。

{
  "version": "2.0.0",
  "tasks": [
    {
      "type": "npm",
      "script": "watch-web",
      "group": "build",
      "isBackground": true,
      "problemMatcher": ["$ts-webpack-watch"]
    }
  ]
}

$ts-webpack-watch は、webpack ツールの出力を解析できる問題マッチャーです。これは TypeScript + Webpack Problem Matchers 拡張機能によって提供されます。

起動する Extension Development Host インスタンスでは、Web 拡張機能が Web 拡張機能ホストで利用可能になり、実行されます。拡張機能をアクティブにするには、Hello World コマンドを実行します。

実行中の拡張機能ビュー (コマンド: 開発者: 実行中の拡張機能を表示) を開いて、どの拡張機能が Web 拡張機能ホストで実行されているかを確認します。

@vscode/test-web を使用してブラウザで Web 拡張機能をテストする

@vscode/test-web node モジュールは、ブラウザで Web 拡張機能をテストするための CLI と API を提供します。

ノードモジュールは、コマンドラインから Web 用 VS Code を開くことができる npm バイナリ vscode-test-web を提供します。

  • VS Code の Web ビットを .vscode-test-web にダウンロードします。
  • localhost:3000 でローカルサーバーを起動します。
  • ブラウザ (Chromium、Firefox、または Webkit) を開きます。

コマンドラインから実行できます。

npx @vscode/test-web --extensionDevelopmentPath=$extensionFolderPath $testDataPath

または、@vscode/test-web を拡張機能の開発依存関係として追加し、スクリプトで呼び出すのが良いでしょう。

  "devDependencies": {
    "@vscode/test-web": "*"
  },
  "scripts": {
    "open-in-browser": "vscode-test-web --extensionDevelopmentPath=. ."
  }

その他の CLI オプションについては、@vscode/test-web README を参照してください。

オプション 引数 説明
--browserType 起動するブラウザ: chromium (デフォルト)、firefox、または webkit
--extensionDevelopmentPath 開発中の拡張機能を含めるパス。
--extensionTestsPath 実行するテストモジュールへのパス。
--permission 開かれたブラウザに付与される権限: 例 clipboard-readclipboard-write
オプションの完全なリストを参照してください。引数は複数回指定できます。
--folder-uri VS Code を開くワークスペースの URI。folderPath が指定されている場合は無視されます。
--extensionPath 追加の拡張機能を含むフォルダを指すパス。
引数は複数回指定できます。
folderPath VS Code を開くローカルフォルダー。
フォルダーの内容は仮想ファイルシステムとして利用可能になり、ワークスペースとして開かれます。

VS Code の Web ビットは .vscode-test-web フォルダーにダウンロードされます。これを .gitignore ファイルに追加することをお勧めします。

vscode.dev で Web 拡張機能をテストする

Web 用 VS Code で誰でも使えるように拡張機能を公開する前に、実際の vscode.dev 環境で拡張機能の動作を確認できます。

vscode.dev で拡張機能を表示するには、まず、vscode.dev がダウンロードして実行できるように、マシンから拡張機能をホストする必要があります。

まず、mkcert をインストールする必要があります。

次に、localhost.pem および localhost-key.pem ファイルを失わない場所に生成します (例: $HOME/certs)。

$ mkdir -p $HOME/certs
$ cd $HOME/certs
$ mkcert -install
$ mkcert localhost

次に、拡張機能のパスから、npx serve を実行して HTTP サーバーを起動します。

$ npx serve --cors -l 5000 --ssl-cert $HOME/certs/localhost.pem --ssl-key $HOME/certs/localhost-key.pem
npx: installed 78 in 2.196s

   ┌────────────────────────────────────────────────────┐
   │                                                    │
   │   Serving!                                         │
   │                                                    │
   │   - Local:            https://:5000       │
   │   - On Your Network:  https://172.19.255.26:5000   │
   │                                                    │
   │   Copied local address to clipboard!               │
   │                                                    │
   └────────────────────────────────────────────────────┘

最後に、vscode.dev を開き、コマンドパレット (⇧⌘P (Windows, Linux Ctrl+Shift+P)) から開発者: 場所から拡張機能をインストール... を実行し、上記から URL (例では https://:5000) を貼り付け、インストールを選択します。

ログを確認する

ブラウザの開発者ツールのコンソールでログを確認し、拡張機能からのエラー、ステータス、ログを確認できます。

vscode.dev 自体からの他のログが表示される場合があります。さらに、ブレークポイントを簡単に設定したり、拡張機能のソースコードを確認したりすることはできません。これらの制限により、vscode.dev でのデバッグは最も快適なエクスペリエンスではないため、vscode.dev にサイドロードする前に、テストには最初の 2 つのオプションを使用することをお勧めします。サイドロードは、拡張機能を公開する前の最終的な健全性チェックとして適しています。

Web 拡張機能のテスト

Web 拡張機能のテストはサポートされており、通常の拡張機能のテストと同様に実装できます。拡張機能テストの基本的な構造については、拡張機能のテストの記事を参照してください。

@vscode/test-web node モジュールは、@vscode/test-electron (以前は vscode-test という名前でした) と同等です。これにより、コマンドラインから Chromium、Firefox、Safari で拡張機能のテストを実行できます。

このユーティリティは次の手順を実行します。

  1. ローカル Web サーバーから Web 用 VS Code エディターを起動します。
  2. 指定されたブラウザを開きます。
  3. 提供されたテストランナースクリプトを実行します。

継続的なビルドでテストを実行して、拡張機能がすべてのブラウザで動作することを確認できます。

テストランナースクリプトは、Web 拡張機能メインファイルと同じ制限を持つ Web 拡張機能ホストで実行されます。

  • すべてのファイルは単一のファイルにバンドルされます。これにはテストランナー (例: Mocha) とすべてのテスト (通常は *.test.ts) が含まれる必要があります。
  • require('vscode') のみがサポートされています。

yo code Web 拡張機能ジェネレーターによって作成される webpack 設定には、テスト用のセクションがあります。これは、./src/web/test/suite/index.ts にテストランナースクリプトを期待しています。提供された テストランナースクリプトは、Mocha の Web バージョンを使用し、すべてのテストファイルをインポートするための webpack 固有の構文を含んでいます。

require('mocha/mocha'); // import the mocha web build

export function run(): Promise<void> {
  return new Promise((c, e) => {
    mocha.setup({
      ui: 'tdd',
      reporter: undefined
    });

    // bundles all files in the current directory matching `*.test`
    const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r);
    importAll(require.context('.', true, /\.test$/));

    try {
      // Run the mocha test
      mocha.run(failures => {
        if (failures > 0) {
          e(new Error(`${failures} tests failed.`));
        } else {
          c();
        }
      });
    } catch (err) {
      console.error(err);
      e(err);
    }
  });
}

コマンドラインから Web テストを実行するには、以下を package.json に追加し、npm test で実行します。

  "devDependencies": {
    "@vscode/test-web": "*"
  },
  "scripts": {
    "test": "vscode-test-web --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js"
  }

テストデータを含むフォルダーで VS Code を開くには、ローカルフォルダーパス (folderPath) を最後のパラメーターとして渡します。

VS Code (Insiders) デスクトップで拡張機能テストを実行 (およびデバッグ) するには、Extension Tests in VS Code 起動設定を使用します。

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Extension Tests in VS Code",
      "type": "extensionHost",
      "debugWebWorkerHost": true,
      "request": "launch",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}",
        "--extensionDevelopmentKind=web",
        "--extensionTestsPath=${workspaceFolder}/dist/web/test/suite/index"
      ],
      "outFiles": ["${workspaceFolder}/dist/web/**/*.js"],
      "preLaunchTask": "npm: watch-web"
    }
  ]
}

Web 拡張機能の公開

Web 拡張機能は、他の拡張機能と同様に Marketplace でホストされています。

拡張機能を公開するには、vsce の最新バージョンを使用していることを確認してください。vsce は、Web 拡張機能であるすべての拡張機能をタグ付けします。そのため、vsceWeb 拡張機能の有効化セクションに記載されているルールを使用しています。

既存の拡張機能を Web 拡張機能に更新する

コードなしの拡張機能

コードがなく、貢献ポイントのみを持つ拡張機能 (たとえば、テーマ、スニペット、基本的な言語拡張機能) は、何の変更も必要ありません。これらは Web 拡張機能ホストで実行でき、拡張機能ビューからインストールできます。

再公開は不要ですが、拡張機能の新しいバージョンを公開する際には、最新の vsce バージョンを使用していることを確認してください。

コードを含む拡張機能を移行する

ソースコードを持つ拡張機能 (main プロパティで定義) は、Web 拡張機能のメインファイルを提供し、package.jsonbrowser プロパティを設定する必要があります。

以下の手順で、ブラウザ環境向けに拡張機能コードを再コンパイルします。

  • webpack の設定セクションに示すように、webpack 構成ファイルを追加します。Node.js 拡張機能コード用にすでに webpack ファイルがある場合は、Web 用の新しいセクションを追加できます。vscode-css-formatter を例として確認してください。
  • Web 拡張機能のテストセクションに示すように、launch.json および tasks.json ファイルを追加します。
  • webpack 設定ファイルで、入力ファイルを既存の Node.js メインファイルに設定するか、Web 拡張機能用の新しいメインファイルを作成します。
  • package.json に、Web 拡張機能の構造セクションに示すように、browser および scripts プロパティを追加します。
  • npm run compile-web を実行して webpack を呼び出し、拡張機能を Web で実行するために必要な作業を確認します。

可能な限り多くのソースコードを再利用するために、いくつかの手法を以下に示します。

  • path などの Node.js コアモジュールをポリフィルするには、resolve.fallback にエントリを追加します。
  • process などの Node.js グローバルを提供するには、DefinePlugin プラグインを使用します。
  • ブラウザとノードの両方のランタイムで動作するノードモジュールを使用します。ノードモジュールは、browsermain の両方のエントリポイントを定義することでこれを行うことができます。Webpack は、そのターゲットに一致するものを自動的に使用します。これを行うノードモジュールの例には、request-light@vscode/l10n があります。
  • ノードモジュールまたはソースファイルの代替実装を提供するには、resolve.alias を使用します。
  • コードをブラウザ部分、Node.js 部分、共通部分に分割します。共通部分では、ブラウザと Node.js ランタイムの両方で動作するコードのみを使用します。Node.js とブラウザで異なる実装を持つ機能には抽象化を作成します。
  • pathURI.filecontext.extensionPathrootPathuri.fsPath の使用に注意してください。これらは、Web 用 VS Code で使用される仮想ワークスペース (非ファイルシステム) では機能しません。代わりに、URI.parsecontext.extensionUri を含む URI を使用してください。vscode-uri ノードモジュールは、joinPathdirNamebaseNameextNameresolvePath を提供します。
  • fs の使用に注意してください。vscode workspace.fs を使用して置き換えてください。

拡張機能が Web で実行されているときに、機能を減らしても問題ありません。when 句コンテキストを使用して、Web 上の仮想ワークスペースで実行中に、どのコマンド、ビュー、タスクが利用可能または非表示になるかを制御します。

  • 現在のワークスペースがファイルシステム以外のワークスペースであるかどうかを確認するには、virtualWorkspace コンテキスト変数を使用します。
  • 現在のリソースが file リソースであるかどうかを確認するには、resourceScheme を使用します。
  • プラットフォームシェルが存在する場合は、shellExecutionSupported を使用します。
  • コマンドが適用できない理由を説明するダイアログを表示する代替コマンドハンドラーを実装します。

WebWorker は、プロセスをフォークする代替手段として使用できます。組み込みの JSONCSS、および HTML 言語サーバーを含む、いくつかの言語サーバーを Web 拡張機能として実行するように更新しました。以下の言語サーバープロトコルセクションで詳細を説明します。

ブラウザのランタイム環境は、JavaScript と WebAssembly の実行のみをサポートしています。他のプログラミング言語で書かれたライブラリはクロスコンパイルする必要があります。たとえば、C/C++Rust を WebAssembly にコンパイルするツールがあります。vscode-anycode 拡張機能は、たとえば、WebAssembly にコンパイルされた C/C++ コードである tree-sitter を使用しています。

Web 拡張機能における言語サーバープロトコル

vscode-languageserver-node は、Language Server Protocol (LSP) の実装であり、JSONCSS、および HTML などの言語サーバー実装の基礎として使用されています。

3.16.0 以降、クライアントとサーバーはブラウザ実装も提供するようになりました。サーバーは Web ワーカーで実行でき、接続は Web ワーカーの postMessage プロトコルに基づいています。

ブラウザ用のクライアントは 'vscode-languageclient/browser' にあります。

import { LanguageClient } from `vscode-languageclient/browser`;

サーバーは vscode-languageserver/browser にあります。

lsp-web-extension-sample は、これがどのように機能するかを示しています。

Web 拡張機能の有効化

VS Code は、次の場合に拡張機能を自動的に Web 拡張機能として扱います。

  • 拡張機能マニフェスト (package.json) に browser エントリポイントがある。
  • 拡張機能マニフェストに main エントリポイントがなく、次の貢献ポイントのいずれもありません: localizationsdebuggersterminaltypescriptServerPlugins

拡張機能が Web 拡張機能ホストでも動作するデバッガーまたはターミナルを提供したい場合は、browser エントリポイントを定義する必要があります。

ESBuild の使用

webpack の代わりに esbuild を使用したい場合は、次の手順を実行します。

esbuild.js ビルドスクリプトを追加します。

const esbuild = require('esbuild');
const glob = require('glob');
const path = require('path');
const polyfill = require('@esbuild-plugins/node-globals-polyfill');

const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');

async function main() {
  const ctx = await esbuild.context({
    entryPoints: ['src/web/extension.ts', 'src/web/test/suite/extensionTests.ts'],
    bundle: true,
    format: 'cjs',
    minify: production,
    sourcemap: !production,
    sourcesContent: false,
    platform: 'browser',
    outdir: 'dist/web',
    external: ['vscode'],
    logLevel: 'warning',
    // Node.js global to browser globalThis
    define: {
      global: 'globalThis'
    },

    plugins: [
      polyfill.NodeGlobalsPolyfillPlugin({
        process: true,
        buffer: true
      }),
      testBundlePlugin,
      esbuildProblemMatcherPlugin /* add to the end of plugins array */
    ]
  });
  if (watch) {
    await ctx.watch();
  } else {
    await ctx.rebuild();
    await ctx.dispose();
  }
}

/**
 * For web extension, all tests, including the test runner, need to be bundled into
 * a single module that has a exported `run` function .
 * This plugin bundles implements a virtual file extensionTests.ts that bundles all these together.
 * @type {import('esbuild').Plugin}
 */
const testBundlePlugin = {
  name: 'testBundlePlugin',
  setup(build) {
    build.onResolve({ filter: /[\/\\]extensionTests\.ts$/ }, args => {
      if (args.kind === 'entry-point') {
        return { path: path.resolve(args.path) };
      }
    });
    build.onLoad({ filter: /[\/\\]extensionTests\.ts$/ }, async args => {
      const testsRoot = path.join(__dirname, 'src/web/test/suite');
      const files = await glob.glob('*.test.{ts,tsx}', { cwd: testsRoot, posix: true });
      return {
        contents:
          `export { run } from './mochaTestRunner.ts';` +
          files.map(f => `import('./${f}');`).join(''),
        watchDirs: files.map(f => path.dirname(path.resolve(testsRoot, f))),
        watchFiles: files.map(f => path.resolve(testsRoot, f))
      };
    });
  }
};

/**
 * This plugin hooks into the build process to print errors in a format that the problem matcher in
 * Visual Studio Code can understand.
 * @type {import('esbuild').Plugin}
 */
const esbuildProblemMatcherPlugin = {
  name: 'esbuild-problem-matcher',

  setup(build) {
    build.onStart(() => {
      console.log('[watch] build started');
    });
    build.onEnd(result => {
      result.errors.forEach(({ text, location }) => {
        console.error(`✘ [ERROR] ${text}`);
        if (location == null) return;
        console.error(`    ${location.file}:${location.line}:${location.column}:`);
      });
      console.log('[watch] build finished');
    });
  }
};

main().catch(e => {
  console.error(e);
  process.exit(1);
});

ビルドスクリプトは以下のことを行います。

  • esbuild でビルドコンテキストを作成します。コンテキストは次のように構成されています。
    • src/web/extension.ts のコードを単一ファイル dist/web/extension.js にバンドルします。
    • テストランナー (mocha) を含むすべてのテストを単一ファイル dist/web/test/suite/extensionTests.js にバンドルします。
    • --production フラグが渡された場合、コードをミニファイします。
    • --production フラグが渡されない限り、ソースマップを生成します。
    • バンドルから 'vscode' モジュールを除外します (VS Code ランタイムによって提供されるため)。
    • processbuffer のポリフィルを作成します。
    • esbuildProblemMatcherPlugin プラグインを使用して、バンドラーの完了を妨げたエラーを報告します。このプラグインは、拡張機能としてインストールする必要がある esbuild 問題マッチャーによって検出される形式でエラーを出力します。
    • testBundlePlugin を使用して、すべてのテストファイルと mocha テストランナー mochaTestRunner.js を参照するテストメインファイル (extensionTests.js) を実装します。
  • --watch フラグが渡された場合、ソースファイルの変更を監視し始め、変更が検出されるたびにバンドルを再構築します。

esbuild は TypeScript ファイルと直接連携できます。ただし、esbuild はタイプチェックを行わずにすべての型宣言を単に削除するだけです。構文エラーのみが報告され、esbuild が失敗する原因となる可能性があります。

そのため、TypeScript コンパイラ (tsc) を個別に実行して型チェックを行いますが、コードは一切出力しません (--noEmit フラグ)。

package.jsonscripts セクションは次のようになります。

  "scripts": {
    "vscode:prepublish": "npm run package-web",
    "compile-web": "npm run check-types && node esbuild.js",
    "watch-web": "npm-run-all -p watch-web:*",
    "watch-web:esbuild": "node esbuild.js --watch",
    "watch-web:tsc": "tsc --noEmit --watch --project tsconfig.json",
    "package-web": "npm run check-types && node esbuild.js --production",
    "check-types": "tsc --noEmit",
    "pretest": "npm run compile-web",
    "test": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/extensionTests.js",
    "run-in-browser": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. ."
  }

npm-run-all は、指定されたプレフィックスに一致するスクリプトを並行して実行する Node モジュールです。私たちの場合、watch-web:esbuildwatch-web:tsc スクリプトを実行します。npm-run-allpackage.jsondevDependencies セクションに追加する必要があります。

以下の tasks.json ファイルは、各ウォッチタスクに個別のターミナルを提供します。

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "watch-web",
      "dependsOn": ["npm: watch-web:tsc", "npm: watch-web:esbuild"],
      "presentation": {
        "reveal": "never"
      },
      "group": {
        "kind": "build",
        "isDefault": true
      },
      "runOptions": {
        "runOn": "folderOpen"
      }
    },
    {
      "type": "npm",
      "script": "watch-web:esbuild",
      "group": "build",
      "problemMatcher": "$esbuild-watch",
      "isBackground": true,
      "label": "npm: watch-web:esbuild",
      "presentation": {
        "group": "watch",
        "reveal": "never"
      }
    },
    {
      "type": "npm",
      "script": "watch-web:tsc",
      "group": "build",
      "problemMatcher": "$tsc-watch",
      "isBackground": true,
      "label": "npm: watch-web:tsc",
      "presentation": {
        "group": "watch",
        "reveal": "never"
      }
    },
    {
      "label": "compile",
      "type": "npm",
      "script": "compile-web",
      "problemMatcher": ["$tsc", "$esbuild"]
    }
  ]
}

これは、esbuild ビルドスクリプトで参照されている mochaTestRunner.js です。

// Imports mocha for the browser, defining the `mocha` global.
import 'mocha/mocha';

mocha.setup({
  ui: 'tdd',
  reporter: undefined
});

export function run(): Promise<void> {
  return new Promise((c, e) => {
    try {
      // Run the mocha test
      mocha.run(failures => {
        if (failures > 0) {
          e(new Error(`${failures} tests failed.`));
        } else {
          c();
        }
      });
    } catch (err) {
      console.error(err);
      e(err);
    }
  });
}

サンプル