🚀 VS Code で で入手しましょう!

Web Extensions

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 拡張機能として作成する場合、VS Code for the Web (vscode.devgithub.dev を含む) だけでなく、デスクトップや GitHub Codespaces のようなサービスでもサポートされます。

Web 拡張機能の構造

web 拡張機能は、通常の拡張機能のように構成されています。拡張機能マニフェスト (package.json) は、拡張機能のソースコードのエントリーファイルを定義し、拡張機能のコントリビューションを宣言します。

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

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

以下の例は、web 拡張機能ホストでのみ実行される (browser エントリーポイントのみを持つ) 簡単な hello world 拡張機能の package.json を示しています。

{
  "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 のみ、main または browser なし) のみの拡張機能は、web 拡張機能にすることができます。これらは、拡張機能の作成者による変更なしに、VS Code for the Web にインストールして実行できます。宣言的なコントリビューションを持つ拡張機能の例には、テーマ、文法、スニペットなどがあります。

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

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

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

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

  • 他のモジュールのインポートまたは require はサポートされていません。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) をサポートする必要があります。
  • 子プロセスの作成や実行可能ファイルの実行はできません。ただし、web worker は Worker API を介して作成できます。これは、「web 拡張機能における Language Server Protocol」セクションで説明されているように、言語サーバーを実行するために使用されます。
  • 通常の拡張機能と同様に、拡張機能の 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 を実行します。

作成される拡張機能は、拡張機能のソースコード (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 拡張機能と、web 拡張機能ホストを備えた VS Code デスクトップでのテストを実行する起動構成が含まれています (extensions.webWorker の設定は不要になりました)。
  • .vscode/task.json には、起動構成で使用されるビルドタスクが含まれています。npm run watch-web を使用し、webpack 固有の ts-webpack-watch problem matcher に依存しています。
  • .vscode/extensions.json には、problem matcher を提供する拡張機能が含まれています。これらの拡張機能は、起動構成が機能するためにインストールする必要があります。
  • 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') を使用して path を定義するプロジェクト内のファイルを指すことができます。または、Browserify node パッケージバージョンのライブラリ path-browserify を使用して、path: require.resolve('path-browserify') を指定できます。
    • Node.js コアモジュールポリフィルのリストについては、webpack resolve.fallback を参照してください。
  • plugins セクションでは、DefinePlugin プラグインを使用して、process Node.js グローバルなどのグローバルをポリフィルします。

Web 拡張機能をテストする

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

  • --extensionDevelopmentKind=web オプションを指定してデスクトップで実行されている VS Code を使用して、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 拡張機能ホストを実行することをサポートしています。

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: watch-web を使用して、npm run 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 ツールの出力を解析できる problem matcher です。これは、TypeScript + Webpack Problem Matchers 拡張機能によって提供されます。

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

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

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

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

この node モジュールは、コマンドラインから VS Code for the Web を開くことができる 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 拡張機能をテストする

VS Code for the Web で誰もが使用できるように拡張機能を公開する前に、実際の 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://localhost: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://localhost: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 拡張機能であるすべての拡張機能にタグを付けます。そのため、vsce は「web 拡張機能の有効化」セクションにリストされているルールを使用しています。

既存の拡張機能を 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 プラグインを使用します。
  • ブラウザーと node ランタイムの両方で動作する node モジュールを使用します。Node モジュールは、browsermain の両方のエントリーポイントを定義することでそれを行うことができます。Webpack は、ターゲットに一致するものを自動的に使用します。これを行う node モジュールの例としては、request-light および @vscode/l10n があります。
  • node モジュールまたはソースファイルの代替実装を提供するには、resolve.alias を使用します。
  • コードをブラウザー部分、Node.js 部分、および共通部分に分離します。共通部分では、ブラウザーと Node.js ランタイムの両方で動作するコードのみを使用します。Node.js とブラウザーで異なる実装を持つ機能の抽象化を作成します。
  • pathURI.filecontext.extensionPathrootPathuri.fsPath の使用箇所に注意してください。これらは、VS Code for the Web で使用されている仮想ワークスペース (非ファイルシステム) では機能しません。代わりに、URI.parsecontext.extensionUri で URI を使用してください。vscode-uri node モジュールは、joinPathdirNamebaseNameextNameresolvePath を提供します。
  • fs の使用箇所に注意してください。vscode workspace.fs を使用して置き換えます。

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

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

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

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

web 拡張機能における Language Server Protocol

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

3.16.0 以降、クライアントとサーバーはブラウザー実装も提供するようになりました。サーバーは web worker で実行でき、接続は web worker の 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 ランタイムによって提供されるため)。
    • process および buffer のポリフィルを作成します。
    • esbuildProblemMatcherPlugin プラグインを使用して、バンドラーの完了を妨げたエラーを報告します。このプラグインは、拡張機能としてインストールする必要もある esbuild problem matcher によって検出される形式でエラーを出力します。
    • 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:esbuild および watch-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);
    }
  });
}

サンプル