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

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 拡張機能ホストのみで実行される (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 拡張機能になることができます。それらは拡張機能の作者による変更なしにWeb 用 VS Code にインストールして実行できます。宣言的な貢献を持つ拡張機能の例には、テーマ、文法、スニペットがあります。

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

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

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

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

  • 他のモジュールのインポートまたは要求はサポートされていません。importScripts も使用できません。結果として、コードは単一のファイルにパッケージ化する必要があります。
  • VS Code APIrequire('vscode') パターンを介してロードできます。これは require のシムがあるため機能しますが、このシムは追加の拡張機能ファイルや追加の Node モジュールをロードするためには使用できません。require('vscode') のみで機能します。
  • process, os, setImmediate, path, util, url などの Node.js グローバルおよびライブラリはランタイムで利用できません。しかし、webpack のようなツールで追加することはできます。webpack の設定セクションでは、これを行う方法を説明しています。
  • 開かれたワークスペースまたはフォルダは仮想ファイルシステム上にあります。ワークスペースファイルへのアクセスは、vscode.workspace.fs でアクセスできる VS Code ファイルシステム API を介して行う必要があります。
  • 拡張機能コンテキストの場所 (ExtensionContext.extensionUri) およびストレージの場所 (ExtensionContext.storageUri, globalStorageUri) も仮想ファイルシステム上にあり、vscode.workspace.fs を介してアクセスする必要があります。
  • Web リソースにアクセスするには、Fetch API を使用する必要があります。アクセスされるリソースはCross-Origin Resource Sharing (CORS) をサポートする必要があります。
  • 子プロセスの作成や実行可能ファイルの実行はできません。しかし、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-web, watch-web, および package-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.jsonwebworker ランタイムに一致するコンパイルオプションを定義します。

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 フィールドには、ブラウザで動作しないノードライブラリのエイリアスとフォールバックを追加する機能が含まれています。
    • 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 拡張機能ホストを実行することをサポートしています。

新しい 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 拡張機能によって提供されます。

起動される Extension Development Host インスタンスでは、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 サーバーから 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.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 があります。
  • ノードモジュールまたはソースファイルの代替実装を提供するには、resolve.alias を使用します。
  • コードをブラウザ部分、Node.js 部分、共通部分に分離します。共通部分では、ブラウザと Node.js ランタイムの両方で動作するコードのみを使用します。Node.js とブラウザで異なる実装を持つ機能には抽象化を作成します。
  • path, URI.file, context.extensionPath, rootPath, uri.fsPath の使用法に注意してください。これらは、Web 用 VS Code で使用される仮想ワークスペース (非ファイルシステム) では機能しません。代わりに URI.parse, context.extensionUri を使用して URI を使用してください。vscode-uri ノードモジュールは joinPath, dirName, baseName, extName, resolvePath を提供します。
  • fs の使用法に注意してください。vscode workspace.fs を使用して置き換えてください。

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

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

WebWorker は、プロセスのフォークの代替として使用できます。組み込みの JSONCSSHTML 言語サーバーを含む、いくつかの言語サーバーを 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) の実装であり、JSONCSSHTML などの言語サーバー実装の基礎として使用されます。

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 エントリポイントがなく、次の貢献ポイントのいずれも含まれていない: localizations, debuggers, terminal, typescriptServerPlugins

拡張機能が 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 の失敗の原因となる可能性があります。

そのため、型チェックのみを行い、コードは出力しない (--noEmit フラグ) ように、TypeScript コンパイラ (tsc) を別途実行します。

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

サンプル

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