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.dev および github.dev を含む) と、デスクトップおよびGitHub Codespaces のようなサービスでサポートされます。

Web 拡張機能の構造

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

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

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

以下の例は、Web 拡張機能ホストでのみ実行されるシンプルな hello world 拡張機能の 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 を明示的にリストする必要があります。

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

Extensions view

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

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

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

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

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

  • 他のモジュールのインポートまたは要求はサポートされていません。importScripts も利用できません。結果として、コードは単一のファイルにパッケージ化される必要があります。
  • VS Code API は、require('vscode') のパターンでロードできます。これは require のためのシムがあるため機能しますが、このシムを使用して追加の拡張ファイルや Node モジュールをロードすることはできません。これは require('vscode') のみで機能します。
  • processossetImmediatepathutilurl などの Node.js グローバルおよびライブラリは、ランタイム時には利用できません。ただし、webpack などのツールで追加できます。webpack の設定 セクションでその方法を説明します。
  • 開かれたワークスペースまたはフォルダーは仮想ファイルシステム上にあります。ワークスペースファイルへのアクセスは、vscode.workspace.fs でアクセスできる VS Code のファイルシステム API を介して行う必要があります。
  • 拡張コンテキストの場所 (ExtensionContext.extensionUri) とストレージの場所 (ExtensionContext.storageUriglobalStorageUri) も仮想ファイルシステム上にあり、vscode.workspace.fs を介してアクセスする必要があります。
  • Web リソースへのアクセスには、Fetch API を使用する必要があります。アクセスされるリソースはCross-Origin Resource Sharing (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 を使用して新しい Web 拡張機能を選択します。generator-code の最新バージョン (>= generator-code@1.6) がインストールされていることを確認してください。ジェネレーターと yo を更新するには、npm i -g yo generator-code を実行します。

作成される拡張機能は、拡張機能のソースコード (hello world 通知を表示するコマンド)、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 拡張機能ホストを備えた VS Code デスクトップで Web 拡張機能とテストを実行する起動構成が含まれています (extensions.webWorker の設定は不要になりました)。
  • .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.js および dist/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') のようにプロジェクト内のファイルを指すことができます。または、Browserify のノードパッケージ版ライブラリである path-browserify を使用して、path: require.resolve('path-browserify') と指定できます。
    • Node.js コアモジュールのポリフィルの一覧については、webpack resolve.fallback を参照してください。
  • plugins セクションでは、DefinePlugin プラグインを使用して、process Node.js グローバルなどのグローバルをポリフィルします。

Web 拡張機能のテスト

現在、Web 拡張機能を Marketplace に公開する前にテストする方法は3つあります。

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

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

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

新しい Web 拡張機能ジェネレーターによって提供される 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 拡張機能によって提供されます。

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

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

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

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

この Node モジュールは、コマンドラインから 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-read, clipboard-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.pemlocalhost-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 サーバーから VS Code for the Web エディターを起動します。
  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.jsontasks.json ファイルを追加します。
  • webpack 設定ファイルで、入力ファイルを既存の Node.js メインファイルに設定するか、Web 拡張機能用に新しいメインファイルを作成します。
  • package.json に、Web 拡張機能の構造セクションに示すように browserscripts プロパティを追加します。
  • npm run compile-web を実行して webpack を起動し、拡張機能が Web で動作するために必要な作業箇所を確認します。

できるだけ多くのソースコードを再利用するために、いくつかのテクニックを紹介します。

  • path のような Node.js コアモジュールをポリフィルするには、resolve.fallback にエントリを追加します。
  • process のような Node.js グローバルを提供するには、DefinePlugin プラグインを使用します。
  • ブラウザとノードランタイムの両方で動作するノードモジュールを使用します。ノードモジュールは、browsermain の両方のエントリポイントを定義することでこれを実現できます。Webpack は、そのターゲットに一致するものを自動的に使用します。これを行うノードモジュールの例としては、request-light@vscode/l10n があります。
  • Node モジュールまたはソースファイルの代替実装を提供するには、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 で実行される場合、機能を減らしても問題ありません。Web 上の仮想ワークスペースで実行されているときに、どのコマンド、ビュー、およびタスクが利用可能または非表示になるかを制御するために、when 句コンテキストを使用します。

  • 現在のワークスペースがファイルシステムではないワークスペースであるかどうかを調べるには、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 は、言語サーバープロトコル (LSP) の実装であり、JSONCSSHTML などの言語サーバー実装の基盤として使用されています。

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);
    }
  });
}

サンプル