拡張機能のバンドル
Visual Studio Code 拡張機能をバンドルする最初の理由は、VS Code を任意のプラットフォームで使用するすべての人にとって動作するようにするためです。バンドルされた拡張機能のみが、github.dev や vscode.dev のような VS Code for Web 環境で使用できます。ブラウザーで VS Code が実行されている場合、拡張機能のファイルを 1 つしか読み込めないため、拡張機能のコードを 1 つの Web フレンドリーな JavaScript ファイルにバンドルする必要があります。これは、VS Code がレンダラー拡張機能のファイルを 1 つしか読み込まない ノートブック出力レンダラー にも適用されます。
さらに、拡張機能はサイズと複雑さが増大する可能性があります。複数のソースファイルで作成され、npm のモジュールに依存する場合もあります。分解と再利用は開発のベストプラクティスですが、拡張機能のインストールと実行にはコストがかかります。100 個の小さなファイルをロードするよりも、1 個の大きなファイルをロードする方がはるかに時間がかかります。そのため、バンドルをお勧めします。バンドルとは、複数の小さなソースファイルを 1 つのファイルに結合するプロセスです。
JavaScript には、さまざまなバンドラーが利用できます。一般的なものとしては、rollup.js、Parcel、esbuild、webpack などがあります。
esbuild を使用する
esbuild
は高速な JavaScript バンドラーで、設定も簡単です。esbuild を入手するには、ターミナルを開いて次のように入力します。
npm i --save-dev esbuild
esbuild の実行
esbuild はコマンドラインから実行できますが、繰り返しを減らし、問題報告を可能にするために、ビルドスクリプト esbuild.js
を使用すると便利です。
const esbuild = require('esbuild');
const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');
async function main() {
const ctx = await esbuild.context({
entryPoints: ['src/extension.ts'],
bundle: true,
format: 'cjs',
minify: production,
sourcemap: !production,
sourcesContent: false,
platform: 'node',
outfile: 'dist/extension.js',
external: ['vscode'],
logLevel: 'warning',
plugins: [
/* add to the end of plugins array */
esbuildProblemMatcherPlugin
]
});
if (watch) {
await ctx.watch();
} else {
await ctx.rebuild();
await ctx.dispose();
}
}
/**
* @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/extension.ts
のコードをdist/extension.js
の単一ファイルにバンドルします。--production
フラグが渡された場合は、コードを圧縮します。--production
フラグが渡されない限り、ソースマップを生成します。- バンドルから「vscode」モジュールを除外します(VS Code ランタイムによって提供されるため)。
- esbuildProblemMatcherPlugin プラグインを使用して、バンドラーの完了を妨げたエラーを報告します。このプラグインは、
esbuild
の問題マッチャーによって検出される形式でエラーを出力します。この問題マッチャーも拡張機能としてインストールする必要があります。 --watch
フラグが渡された場合、ソースファイルの変更を監視し、変更が検出されるたびにバンドルを再構築します。
esbuild は TypeScript ファイルを直接扱うことができます。ただし、esbuild は型チェックを行わずにすべての型宣言を単純に削除します。構文エラーのみが報告され、esbuild が失敗する原因となる可能性があります。
そのため、TypeScript コンパイラ (tsc
) を別途実行して型チェックを行いますが、コードは出力しません (--noEmit
フラグ)。
package.json
の scripts
セクションは次のようになります。
"scripts": {
"compile": "npm run check-types && node esbuild.js",
"check-types": "tsc --noEmit",
"watch": "npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"vscode:prepublish": "npm run package",
"package": "npm run check-types && node esbuild.js --production"
}
npm-run-all
は、指定されたプレフィックスに一致する名前のスクリプトを並行して実行する Node モジュールです。私たちの場合、watch:esbuild
と watch:tsc
スクリプトを実行します。npm-run-all
を package.json
の devDependencies
セクションに追加する必要があります。
compile
および watch
スクリプトは開発用であり、ソースマップ付きのバンドルファイルを生成します。package
スクリプトは、VS Code のパッケージングおよび公開ツールである vsce
によって使用される vscode:prepublish
スクリプトによって使用され、拡張機能の公開前に実行されます。esbuild スクリプトに --production
フラグを渡すと、コードが圧縮され、小さなバンドルが作成されますが、デバッグが困難になるため、開発中は他のフラグが使用されます。上記のスクリプトを実行するには、ターミナルを開いて npm run watch
と入力するか、コマンドパレット (⇧⌘P (Windows, Linux Ctrl+Shift+P)) から **Tasks: Run Task** を選択します。
.vscode/tasks.json
を次のように設定すると、各ウォッチタスクに対して個別のターミナルが得られます。
{
"version": "2.0.0",
"tasks": [
{
"label": "watch",
"dependsOn": ["npm: watch:tsc", "npm: watch:esbuild"],
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "watch:esbuild",
"group": "build",
"problemMatcher": "$esbuild-watch",
"isBackground": true,
"label": "npm: watch:esbuild",
"presentation": {
"group": "watch",
"reveal": "never"
}
},
{
"type": "npm",
"script": "watch:tsc",
"group": "build",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"label": "npm: watch:tsc",
"presentation": {
"group": "watch",
"reveal": "never"
}
}
]
}
このウォッチタスクは、問題ビューで問題を報告するためにインストールする必要がある、問題マッチング用の拡張機能 connor4312.esbuild-problem-matchers
に依存しています。この拡張機能は、起動を完了するためにインストールする必要があります。
それを忘れないように、ワークスペースに .vscode/extensions.json
ファイルを追加します。
{
"recommendations": ["connor4312.esbuild-problem-matchers"]
}
最後に、コンパイルされたファイルが公開される拡張機能に含まれるように、.vscodeignore
ファイルを更新する必要があります。詳細については、「公開」セクションを参照してください。
テスト セクションにジャンプして読み進めてください。
webpack を使用する
Webpack は npm から利用できる開発ツールです。webpack とそのコマンドラインインターフェースを取得するには、ターミナルを開いて次のように入力します。
npm i --save-dev webpack webpack-cli
これにより、webpack がインストールされ、拡張機能の package.json
ファイルが更新され、devDependencies
に webpack が含まれるようになります。
Webpack は JavaScript バンドラーですが、多くの VS Code 拡張機能は TypeScript で記述されており、JavaScript にコンパイルされるだけです。拡張機能が TypeScript を使用している場合は、ローダー ts-loader
を使用することで、webpack が TypeScript を理解できるようになります。ts-loader
をインストールするには、次を使用します。
npm i --save-dev ts-loader
すべてのファイルは webpack-extension サンプルで利用できます。
webpack を設定する
すべてのツールがインストールされたら、webpack を設定できます。慣例として、webpack.config.js
ファイルには、webpack に拡張機能をバンドルするように指示する設定が含まれています。以下のサンプル設定は VS Code 拡張機能用で、良い出発点となるはずです。
//@ts-check
'use strict';
const path = require('path');
const webpack = require('webpack');
/**@type {import('webpack').Configuration}*/
const config = {
target: 'webworker', // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.dokyumento.jp/configuration/target/#target
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.dokyumento.jp/configuration/entry-context/
output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.dokyumento.jp/configuration/output/
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
libraryTarget: 'commonjs2',
devtoolModuleFilenameTemplate: '../[resource-path]'
},
devtool: 'source-map',
externals: {
vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.dokyumento.jp/configuration/externals/
},
resolve: {
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules
extensions: ['.ts', '.js'],
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.
}
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
}
]
}
};
module.exports = config;
このファイルは、webpack-extension サンプルの一部として利用できます。Webpack の設定ファイルは、設定オブジェクトをエクスポートする必要がある通常の JavaScript モジュールです。
上記のサンプルでは、以下のものが定義されています。
target
は、拡張機能が実行されるコンテキストを示します。拡張機能が Web 用の VS Code とデスクトップ版の VS Code の両方で動作するように、webworker
を使用することをお勧めします。- webpack が使用するエントリポイントです。これは
package.json
のmain
プロパティに似ていますが、webpack に「出力」エントリポイントではなく、「ソース」エントリポイント(通常はsrc/extension.ts
)を提供します。webpack バンドラーは TypeScript を理解するため、TypeScript の別途コンパイルステップは冗長です。 output
の設定は、生成されたバンドルファイルをどこに配置するかを webpack に指示します。慣例により、それはdist
フォルダーです。このサンプルでは、webpack はdist/extension.js
ファイルを生成します。resolve
およびmodule/rules
の設定は、TypeScript および JavaScript の入力ファイルをサポートするために存在します。externals
設定は、バンドルに含めるべきではないファイルやモジュールなど、除外を宣言するために使用されます。vscode
モジュールはディスク上には存在せず、必要に応じて VS Code によってその場で作成されるため、バンドルすべきではありません。拡張機能が使用する Node モジュールによっては、さらなる除外が必要になる場合があります。
最後に、コンパイルされたファイルが公開される拡張機能に含まれるように、.vscodeignore
ファイルを更新する必要があります。詳細については、「公開」セクションを参照してください。
webpack を実行する
webpack.config.js
ファイルが作成されたら、webpack を呼び出すことができます。webpack はコマンドラインから実行できますが、繰り返しを減らすために、npm スクリプトを使用すると便利です。
これらのエントリを package.json
の scripts
セクションにマージします。
"scripts": {
"compile": "webpack --mode development",
"watch": "webpack --mode development --watch",
"vscode:prepublish": "npm run package",
"package": "webpack --mode production --devtool hidden-source-map",
},
compile
および watch
スクリプトは開発用であり、バンドルファイルを生成します。vscode:prepublish
は、VS Code のパッケージングおよび公開ツールである vsce
によって使用され、拡張機能の公開前に実行されます。違いは モード にあり、最適化のレベルを制御します。production
を使用すると最小のバンドルが得られますが、時間もかかります。そのため、development
が使用されます。上記のスクリプトを実行するには、ターミナルを開いて npm run compile
と入力するか、コマンドパレット (⇧⌘P (Windows, Linux Ctrl+Shift+P)) から **Tasks: Run Task** を選択します。
拡張機能の実行
拡張機能を実行する前に、package.json
の main
プロパティがバンドルを指すようにする必要があります。上記の構成の場合、これは "./dist/extension"
です。この変更により、拡張機能を実行およびテストできるようになります。
テスト
拡張機能の作者は、拡張機能のソースコードに対して単体テストを作成することがよくあります。拡張機能のソースコードがテストに依存しないように、適切なアーキテクチャレイヤーがあれば、webpack と esbuild が生成するバンドルにはテストコードが含まれないはずです。単体テストを実行するには、単純なコンパイルだけで十分です。
これらのエントリを package.json
の scripts
セクションにマージします。
"scripts": {
"compile-tests": "tsc -p . --outDir out",
"pretest": "npm run compile-tests",
"test": "vscode-test"
}
compile-tests
スクリプトは TypeScript コンパイラを使用して拡張機能を out
フォルダーにコンパイルします。その中間 JavaScript が利用可能であれば、launch.json
の以下のスニペットでテストを実行するのに十分です。
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test"
],
"outFiles": ["${workspaceFolder}/out/test/**/*.js"],
"preLaunchTask": "npm: compile-tests"
}
テストを実行するためのこの構成は、バンドルされていない拡張機能と同じです。単体テストは拡張機能の公開部分に含まれないため、バンドルする必要はありません。
公開
公開する前に、.vscodeignore
ファイルを更新する必要があります。dist/extension.js
ファイルにバンドルされるものはすべて除外できます。通常、out
フォルダー(まだ削除していない場合)と、最も重要な node_modules
フォルダーです。
一般的な .vscodeignore
ファイルは次のようになります。
.vscode
node_modules
out/
src/
tsconfig.json
webpack.config.js
esbuild.js
既存の拡張機能を移行する
既存の拡張機能を esbuild または webpack を使用するように移行するのは簡単で、上記の入門ガイドと似ています。webpack を採用した実際のサンプルは、このプルリクエストによる VS Code の References ビューです。
そこで、次のことがわかります。
esbuild
またはwebpack
、webpack-cli
、ts-loader
をdevDependencies
として追加します。- 上記のようにバンドラーを使用するように npm スクリプトを更新します。
- タスク設定
tasks.json
ファイルを更新します。 esbuild.js
またはwebpack.config.js
ビルドファイルを追加し、調整します。.vscodeignore
を更新して、node_modules
と中間出力ファイルを除外します。- インストールと読み込みがはるかに高速になった拡張機能をお楽しみください!
トラブルシューティング
ミニフィケーション
production
モードでのバンドルは、コードのミニフィケーションも実行します。ミニフィケーションは、空白とコメントを削除し、変数名と関数名を醜いが短いものに変更することによって、ソースコードをコンパクトにします。Function.prototype.name
を使用するソースコードは動作が異なるため、ミニフィケーションを無効にする必要がある場合があります。
webpack のクリティカルな依存関係
webpack の実行中に、「**Critical dependencies: the request of a dependency is an expression**」のような警告が表示されることがあります。このような警告は真剣に受け止めるべきであり、おそらくバンドルは機能しません。このメッセージは、webpack が一部の依存関係をどのようにバンドルするかを静的に判断できないことを意味します。これは通常、動的な require
ステートメント、例えば require(someDynamicVariable)
によって引き起こされます。
警告に対処するには、次のいずれかを実行します。
- バンドルできるように、依存関係を静的にするように試みます。
externals
設定を介してその依存関係を除外します。また、それらの JavaScript ファイルがパッケージ化された拡張機能から除外されないように、.vscodeignore
で否定のグロブパターンを使用してください。例えば、!node_modules/mySpecialModule
などです。
次のステップ
- 拡張機能マーケットプレイス - VS Code のパブリック拡張機能マーケットプレイスについて詳しく学びましょう。
- 拡張機能のテスト - 高品質を確保するために、拡張機能プロジェクトにテストを追加しましょう。
- 継続的インテグレーション - Azure Pipelines で拡張機能の CI ビルドを実行する方法を学びましょう。