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 拡張機能ホストでのみ実行されるシンプルなハローワールド拡張機能の 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 バージョンを対象とする場合、
activationEvents
にonCommand:helloworld-web-sample.helloWorld
を明示的にリストする必要があります。
browser
を持たず main
エントリポイントのみを持つ拡張機能は、Web 拡張機能ではありません。それらは Web 拡張機能ホストによって無視され、拡張機能ビューでダウンロードできません。
宣言的な寄与のみを持つ拡張機能 (contributes
のみで、main
または browser
を持たない拡張機能) は Web 拡張機能となり得ます。これらは、拡張機能の作成者による変更なしに、Web 版 VS Code にインストールして実行できます。宣言的な寄与を持つ拡張機能の例には、テーマ、文法、スニペットなどがあります。
拡張機能は、ブラウザと Node.js ランタイムの両方で実行するために、browser
と main
の両方のエントリポイントを持つことができます。既存の拡張機能を Web 拡張機能に更新する セクションでは、両方のランタイムで動作するように拡張機能を移行する方法を示しています。
Web 拡張機能の有効化セクションには、拡張機能が Web 拡張機能ホストに読み込まれるかどうかを決定するために使用されるルールがリストされています。
Web 拡張機能のメインファイル
Web 拡張機能のメインファイルは、browser
プロパティで定義されます。スクリプトは、ブラウザ WebWorker 環境の Web 拡張機能ホストで実行されます。これは、ブラウザワーカーのサンドボックスによって制限され、Node.js ランタイムで実行される通常の拡張機能と比較して制限があります。
- 他のモジュールのインポートまたは要求はサポートされていません。
importScripts
も利用できません。結果として、コードは単一のファイルにパッケージ化する必要があります。 - VS Code API は
require('vscode')
パターンを介してロードできます。これはrequire
のシムがあるため機能しますが、このシムは追加の拡張機能ファイルや追加のノードモジュールをロードするためには使用できません。これは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 を使用する必要があります。アクセスされるリソースは、クロスオリジンリソース共有 (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-web
、watch-web
、package-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 から始めます。
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.json
のmain
で現在使用しているファイルを指すことから始めることができます。 - テストをパッケージ化しない場合は、テストスイートフィールドを省略できます。
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 ノードパッケージ版のライブラリ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-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.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 で拡張機能のテストを実行できます。
このユーティリティは次の手順を実行します。
- ローカル Web サーバーから Web 用 VS Code エディターを起動します。
- 指定されたブラウザを開きます。
- 提供されたテストランナースクリプトを実行します。
継続的なビルドでテストを実行して、拡張機能がすべてのブラウザで動作することを確認できます。
テストランナースクリプトは、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.json
で browser
プロパティを設定する必要があります。
以下の手順で、ブラウザ環境向けに拡張機能コードを再コンパイルします。
- 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 プラグインを使用します。- ブラウザとノードの両方のランタイムで動作するノードモジュールを使用します。ノードモジュールは、
browser
とmain
の両方のエントリポイントを定義することでこれを行うことができます。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
の使用に注意してください。vscodeworkspace.fs
を使用して置き換えてください。
拡張機能が Web で実行されているときに、機能を減らしても問題ありません。when 句コンテキストを使用して、Web 上の仮想ワークスペースで実行中に、どのコマンド、ビュー、タスクが利用可能または非表示になるかを制御します。
- 現在のワークスペースがファイルシステム以外のワークスペースであるかどうかを確認するには、
virtualWorkspace
コンテキスト変数を使用します。 - 現在のリソースが
file
リソースであるかどうかを確認するには、resourceScheme
を使用します。 - プラットフォームシェルが存在する場合は、
shellExecutionSupported
を使用します。 - コマンドが適用できない理由を説明するダイアログを表示する代替コマンドハンドラーを実装します。
WebWorker は、プロセスをフォークする代替手段として使用できます。組み込みの JSON、CSS、および HTML 言語サーバーを含む、いくつかの言語サーバーを Web 拡張機能として実行するように更新しました。以下の言語サーバープロトコルセクションで詳細を説明します。
ブラウザのランタイム環境は、JavaScript と WebAssembly の実行のみをサポートしています。他のプログラミング言語で書かれたライブラリはクロスコンパイルする必要があります。たとえば、C/C++ や Rust を WebAssembly にコンパイルするツールがあります。vscode-anycode 拡張機能は、たとえば、WebAssembly にコンパイルされた C/C++ コードである tree-sitter を使用しています。
Web 拡張機能における言語サーバープロトコル
vscode-languageserver-node は、Language Server Protocol (LSP) の実装であり、JSON、CSS、および 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
エントリポイントがなく、次の貢献ポイントのいずれもありません: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 ランタイムによって提供されるため)。
process
とbuffer
のポリフィルを作成します。- esbuildProblemMatcherPlugin プラグインを使用して、バンドラーの完了を妨げたエラーを報告します。このプラグインは、拡張機能としてインストールする必要がある
esbuild
問題マッチャーによって検出される形式でエラーを出力します。 - testBundlePlugin を使用して、すべてのテストファイルと mocha テストランナー
mochaTestRunner.js
を参照するテストメインファイル (extensionTests.js
) を実装します。
--watch
フラグが渡された場合、ソースファイルの変更を監視し始め、変更が検出されるたびにバンドルを再構築します。
esbuild は TypeScript ファイルと直接連携できます。ただし、esbuild はタイプチェックを行わずにすべての型宣言を単に削除するだけです。構文エラーのみが報告され、esbuild が失敗する原因となる可能性があります。
そのため、TypeScript コンパイラ (tsc
) を個別に実行して型チェックを行いますが、コードは一切出力しません (--noEmit
フラグ)。
package.json
の scripts
セクションは次のようになります。
"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-all
を package.json
の devDependencies
セクションに追加する必要があります。
以下の 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);
}
});
}