Visual Studio Codeの拡張機能を開発する - テンプレートの作成方法とその解説

この記事ではVisual Studio Code(以下VSCode)の拡張機能の開発方法について、その環境構築およびテンプレートの作成方法と、簡単にそのコードの解説を行います。

開発に必要な環境

VSCodeの拡張機能の開発を行うには下記環境が必要になります。

  • Node.jsおよびnpm
  • Visual Studio Code

VS Code Extension Generatorによるテンプレートの作成

公式では、拡張機能のテンプレートはYaomanVS Code Extension Generatorを用いて作成することを推奨しています。

まず、上記のソフトウェアをインストールします。

$ npm install -g yo generator-code

インストール後、下記のコマンドを実行してテンプレートを作成します。コマンドを実行後はいくつか拡張機能のプロジェクト設定について確認がされるので、それに答えていきます。

$ yo code
yo code

     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension  │
   `---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |     
   __'.___.'__   
 ´   `  |° ´ Y ` 

? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? HelloWorld
? What's the identifier of your extension? helloworld
? What's the description of your extension? My first extension
? Initialize a git repository? Yes
? Bundle the source code with webpack? No
? Which package manager to use? npm

Writing in /Users/yucchiy/helloworld...
   create .vscode/extensions.json
   create .vscode/launch.json
   create .vscode/settings.json
   create .vscode/tasks.json
   create src/test/runTest.ts
   create src/test/suite/extension.test.ts
   create src/test/suite/index.ts
   create .vscodeignore
   create .gitignore
   create README.md
   create CHANGELOG.md
   create vsc-extension-quickstart.md
   create tsconfig.json
   create src/extension.ts
   create package.json
   create .eslintrc.json

上記の質問では

  • ? What type of extension do you want to create?
    • New Extension (TypeScript)
    • TypeScriptベースで拡開発を行うためにTypeScriptを選択。(他にもJavaScriptベースでの拡張開発や、カラーテーマの作成など種類がある)
    • 好みでJavaScriptでもよい。
  • ? What's the name of your extension?
    • HelloWorld
    • 拡張名の設定。Marketplaceや拡張一覧などででてくる名前表示に利用される。
  • ? What's the identifier of your extension?
    • helloworld
    • 拡張のID。適宜変更する。
  • ? What's the description of your extension?
    • My first extension
    • 拡張の説明文。
  • ? Initialize a git repository?
    • Yes
    • Gitレポジトリを初期化するかどうか。基本OKで良さそう。
  • ? Bundle the source code with webpack?
    • No
    • Webpackでソースコードをバンドルするかどうか。お好みですがなくても開発できそうなので今回はオフで。
    • WebViewベースのアプリを作りたいならいれたほうがいい?
  • ? Which package manager to use?
    • npm
    • パッケージマネージャーの選択。現在ではnpmとyarnが選択できる。今どきならyarn?(といいつつ今回はnpmを選択)

について答えています。

質問に答えると拡張のテンプレートがカレントディレクトリに作成されます。IDをhelloworldとしたので、helloworldという名前のディレクトリが作成されているはずです。

テンプレートの動作確認とデバッグ方法について

テンプレートが作成できたのでとりあえず動かしてみましょう。まず作成されたテンプレートであるhelloworldディレクトリをVSCodeで開きます。

ひらいたら、F5を押して拡張をデバッグ実行します。すると拡張のコンパイルなどがはじまり、それが終わると今開いているVSCodeのウインドウとは別に[Extension Development Host]という名前のついたVSCodeが起動します。

F5を押して拡張をデバッグ実行している様子

F5を押して拡張をデバッグ実行している様子

こちらのウインドウは拡張をインストール済みのウインドウで、こちらで拡張の動作確認を行うことができます。試しにテンプレートで実装されているコマンドを呼び出してみます。

実装されているコマンドを呼び出す様子。実行後右下にメッセージウインドウが表示されていることがわかる。

デバッグ実行時にはソースコード中にブレークポイントを貼ったり、コードをステップ実行できます。

ブレークポイントを貼ってデバッグを行う様子

作成されたテンプレートの構成

作成されたテンプレートの中身をのぞいて構成について把握します。作成されたテンプレートは(2021/04/27現在では)下記の構成になっています。

$ tree -L 1
.
├── CHANGELOG.md
├── README.md
├── node_modules
├── package-lock.json
├── package.json
├── src
├── tsconfig.json
└── vsc-extension-quickstart.md

上記からわかるように、VSCode拡張はnpmのパッケージとして管理されています。(テンプレート作成時にパッケージマネジャーとしてnpmを選択したので当然といえば当然ですが)

更にsrc以下は下記のようになっています。

$ tree ./src    
./src
├── extension.ts
└── test
    ├── runTest.ts
    └── suite
        ├── extension.test.ts
        └── index.ts

拡張の開発において、特に重要なのが下記です。

  • package.json
  • src/extension.ts

src/extension.ts

src/extension.tsは、拡張のエントリーポイントとなるファイルです。中身は下記のようになっています。

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

	// Use the console to output diagnostic information (console.log) and errors (console.error)
	// This line of code will only be executed once when your extension is activated
	console.log('Congratulations, your extension "helloworld" is now active!');

	// The command has been defined in the package.json file
	// Now provide the implementation of the command with registerCommand
	// The commandId parameter must match the command field in package.json
	let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
		// The code you place here will be executed every time your command is executed

		// Display a message box to the user
		vscode.window.showInformationMessage('Hello World from HelloWorld!');
	});

	context.subscriptions.push(disposable);
}

// this method is called when your extension is deactivated
export function deactivate() {}

activatedeactivateが定義されていますが、これらはそれぞれ拡張をロードおよびアンロードするときに呼び出される関数になっています。つまりこのなかで拡張の初期化および破棄を記述することになります。

前述の通りこの拡張ではHello Worldというコマンドを実行するとメッセージが表示されましたが、その処理を行っているのが下記になります。

// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = vscode.commands.registerCommand('helloworld.helloWorld', () => {
	// The code you place here will be executed every time your command is executed

	// Display a message box to the user
	vscode.window.showInformationMessage('Hello World from HelloWorld!');
});

vscode.commands.registerCommandというメソッドでhelloworld.helloWorldというコマンド名に対して、無名関数で定義した処理を呼び出すように登録しています。

関数内部ではvscode.window.showInformationMessageを呼び出しています。これにより右下にポップアップでメッセージを出力できます。

コマンド実装の説明はこれだけと、とてもシンプルなのですが、vscode.commands.registerCommandの戻り値について補足しておきます。

この関数の戻り値はDisposableになっています。Disposableなにかの破棄の処理を行うdisposeメソッドを持つインターフェイスで、これを呼び出すことでリソースの開放やイベントの購読解除を行います。(具体的なdisposeの実装はそのオブジェクトの用途次第です。)

vscode.commands.registerCommanの戻り値ではこのコマンド登録を解除するためのDisposableが返却されます。つまり、このDisposableを適切なタイミングで呼んであげないとコマンドの解除ができないわけですが、VSCodeではactivate関数で引数として受け取ることのできるcontextというオブジェクトにsubscriptionsという配列が存在していて、その中に戻り値を追加しておくことで、その拡張が無効になったときに追加したオブジェクトのdisposeが自動で呼び出すようになっています。

このようにVSCodeの拡張実装のためのAPIの多くは、各種登録API呼び出しの際にDisposableを戻り値として返すので、context.subscriptionsに登録しておくことで破棄を実装することが多いようです。 こうすることで、初期化に対する破棄の処理がコード中で近い位置に配置できるため、破棄わすれによるリソース開放や購読解除わすれを阻止するのが狙いなのかなと感じています。

上記の理由により、VSCodeの拡張実装の多くではdeactiveの実装が空ないし定義しないものが多い印象です。(例: omnisharp-vscodeのactivate実装

package.json

次にpackage.jsonについて触れておきます。この中でも特に拡張に関わる項目は下記になります。

  • main
  • activationEvents
  • contributes.commands

まずmainについてですが、これは拡張のエントリーポイントを定義しています。デフォルトでは下記となっています。

	"main": "./out/extension.js",

これは先ほど紹介したsrc/extension.tsをコンパイルしたJavaScriptファイルを参照しています。このようにactivate(とdeactivate)を実装したモジュールを指定します。 この項目は特に変更する必要はないですが、プロジェクトの配置上変更したいなどある場合に、適宜変更することができます。

次にactivationEventsおよびcontributes.commandsについてですが、事前にその拡張が利用するコマンドをpackage.jsonに指定する必要があり、 コマンドを追加・変更する際それぞれactivationEventscontributes.commandに登録する必要があります。

	"activationEvents": [
		"onCommand:helloworld.helloWorld"
	],
	"contributes": {
		"commands": [
			{
				"command": "helloworld.helloWorld",
				"title": "Hello World"
			}
		]
	},

ここで登録を忘れるとsrc/extension.tsregisterCommandを行ってもコマンドを実行することができないので、その点に注意が必要です。

まとめ

VSCodeの拡張についてその環境の構築方法と、テンプレートが生成するコマンドについて解説しました。

参考