VSCodeの拡張機能を作る手順
前説
本記事は、下記で紹介したVSCode拡張機能をリリースするにあたって、プロジェクトの作成から公開、その他気にしておくべきことなどをまとめたものになります。
目次
- 前説
- 雑文
- 開発環境
- プロジェクトの作成
- 実行とデバッグ
- 多言語対応
- 拡張機能のテスト
- パッケージングとインストール
- 拡張機能の公開
- GitHub Actions を使ったCD/CI
- CHANGELOG のこと
- まとめ
- 参考記事
雑文
もはや我々デベロッパーの日常とは切っても切り離せない「VSCode」。毎日お世話になっております!
VSCodeには開発する言語やミドルウェアに応じた無数の拡張機能(Extensions)が提供されていてとても便利です。
とても便利なんですが、使っているとときどき「あ、これできたら便利なのに拡張機能ないのか・・・」「これ手作業で書いてるけど自動でできたら便利だな・・・」のようにちょっとモヤモヤすること、ないでしょうか。
そんなVSCodeのかゆいところに手が届く拡張機能をDIYで開発する手順をご紹介します。
文筆家にとってのペン、料理人にとっての包丁のような、我々デベロッパーの商売道具を、自分で作った拡張機能で自分色に染めあげてみてはいかがでしょうか。(過言)
開発環境
$ volta -v && node -v && npm -v
1.1.1
v18.15.0
9.5.0
YEOMANとgenerator-codeのインストール
YEOMANは言語に依存しない汎用的な、プロジェクトのscaffoldingを行うツールです。
VSCode拡張機能を作るスタートラインとして、このYEOMAN(yo
)と、YEOMAN用のジェネレータであるgenerator-code
を使います。
下記のコマンドでこれらをインストールします。
npm install -g yo generator-code
プロジェクトの作成
下記のコマンドでyo
を実行するとプロジェクトの作成が始まります。
yo code
ここでヨーマンの登場。
プロジェクト作成のため、いろいろなオプションを訪ねてきます。
ここでは下記のように選択/入力してみました。
? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? sample-extension
? What's the identifier of your extension? sample-extension
? What's the description of your extension? This extension is sample.
? Initialize a git repository? Yes
? Bundle the source code with webpack? Yes
? Which package manager to use? pnpm
いくつか補足しますと、
-
identifier
はデフォルトで名称と同じになります。 -
description
はこの時点では未入力でもOKです。あとでpackage.json
に追記できます。 - webpackは拡張機能としてビルドするなら必須のようです。
- nodeのパッケージマネージャはnpm、yarn、pnpmのいずれかから選択です。
- 最後に
Open with code
かskip
かを選択できます。Open with code
を選択すると、プロジェクトディレクトリが別ウィンドウで開きます。
ここまでの作業でプロジェクトのひな形が作成されます。下記のような構造です。
実行とデバッグ
ここでおもむろにF5キーをプッシュ、もしくはVSCodeの「実行とデバッグ」からRun Extension
の▷
をクリックします。
VSCodeの新しいウィンドウが起動しますので、Command + Shift + p でコマンドパレットを開き、
hello
と入力します。
Enterして「Hello World」を実行すると、なにやらメッセージが表示されました!
いま実行された拡張機能の本体はsrc/extension.ts
です。ブレークポイントを設定するとデバッグすることもできます。
vscode
というライブラリがインポートされていて、ウィンドウ上にメッセージを表示させているのが読み取れます。
あとはこのファイルに必要な処理を実装していけばOKです。
関数とVSCodeのコマンドの紐付け
先ほどのソースを見ますと、'sample-extension.helloWorld'
という名前でvscode
のcommands
にコマンドをregist
しているのがわかります。
export function activate(context: vscode.ExtensionContext) {
console.log('Congratulations, your extension "sample-extension" is now active!');
// 👇の行
let disposable = vscode.commands.registerCommand('sample-extension.helloWorld', () => {
vscode.window.showInformationMessage('Hello World from sample-extension!');
});
context.subscriptions.push(disposable);
}
この名前はpackage.json
に記述されていて、
"contributes": {
"commands": [
{
"command": "sample-extension.helloWorld", // <-ココ
"title": "Hello World"
}
]
},
上記のように、"Hello World"
という名称と、"sample-extension.helloWorld"
というコマンドを紐づけています。
これは、
- package.json
-
title
とcommand
名を紐付け
-
- extension.ts
-
command
名と実際の関数(function
)を紐付け
-
のような流れで、VSCode上に表示されるコマンド名と、実際の処理を紐づけているという理解です。
したがって、もう1つ別の機能を追加しようと思った場合、
例えばpackage.json
をこうして、
実装をこうすると、
こうできちゃうわけです。
ここではコマンドパレットから実行できるようにcommand
という指示を使いましたが、VSCodeの拡張機能開発ではこれらの指示はContribution Points
と呼ばれ、他にも多数の種類が用意されています。
例えば、拡張機能をエディタ上で右クリックメニュー(コンテキストメニュー)から実行したい場合は次のようにします。
"menus": {
"editor/context": [
{
"when": "editorFocus",
"command": "sample-extension.newCommand",
"group": "myGroup@1"
},
その他のContribution Points
については下記の公式ドキュメントをご参考ください。
多言語対応
拡張機能を日本語化するための方法を紹介します。
extension.ts
がimportしているvscode
モジュールはvscode.env.language
というプロパティを持っており、実行環境の言語コードはここで知ることができます。
例えばなんらかのメッセージを表示する機能であれば、この言語コードを参照してメッセージを切り替えることはそれほど難しくありません。
ただしコマンド名そのものや、コンテキストメニュー上に表示されるラベルを切り替えたい場合、この方法ではちょっと難しいです。
メニューやタイトルの場合
まず、メニューや拡張機能のタイトルなど、package.json
に記述する項目の多言語化です。
- 項目のkeyを決める(
commandName.helloWorld
など) -
package.json
内の該当の文字列を、%{key名}%
に置き換える(%commandName.helloWorld%
など) -
package.nls.json
、および対応したい言語のロケールごとにpackage.nls.{locale}.json
を作る - それぞれのファイルにjson形式で、
key:文字列
のように表示したい文字列を書いていく
{
"commandName.helloWorld": "Hello World"
}
{
"commandName.helloWorld": "こんにちは世界"
}
"contributes": {
"commands": [
{
"command": "sample-extension.helloWorld",
"title": "%commandName.helloWorld%" // key名を"%"でかこんで対応させる
}
]
},
extension.ts
)のテキスト
プログラム内(さらにこれらpackage.nls.{locale}.json
の内容をプログラム中でも使ってみます。
こんなコードを書きます。
import * as vscode from 'vscode';
import localeDefault from '../package.nls.json';
import localeJa from '../package.nls.ja.json';
export type LocaleKeyType = keyof typeof localeDefault;
interface LocaleEntry
{
[key: string]: string;
}
const localeTableKey = vscode.env.language;
const localeTable = Object.assign(localeDefault, ((<{[key : string] : LocaleEntry}>{
ja : localeJa
})[localeTableKey] || { }));
const localeString = (key : string) : string => localeTable[key] || key;
export const localeMap = (key : LocaleKeyType) : string => localeString(key);
上記のコード、記述した時点ではいくつかエラーが出ていることかと思います。
そこでtsconfig.json
を下記のように編集します。
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"lib": [
"ES2020"
],
"sourceMap": true,
- "rootDir": "src",
+ "rootDir": ".", // ルートを1段上げて package.nls.json をソースに含めます。
+ "outDir": "dist",
"strict": true,
+ "resolveJsonModule": true, // jsonファイルをモジュールとして扱えるようにします。
+ "esModuleInterop": true ,
- }
+ },
+ "exclude": [ // ルートが1段上がったので、関係ないディレクトリをビルド対象から除外します。
+ ".vscode",
+ "node_modules",
+ "dist",
+ "./src/test/**/*",
+ ]
}
エラーは消えましたでしょうか。
先ほどのlocaleMap
、2つの*.nls.json
を読み込み、localeMap("{key名}")
のようにして各jsonに書かれているテキストを読み出すことができるようになっています。
言語ロケールの判断はモジュール内で行いますので、自動的に現在の言語に応じたテキストが設定されます。
今回の場合はlocaleMap("command.message.helloWorld")
のように指定すると、対応するテキストをコード中で利用できます。
以上の手順で、メニューやコマンド上の表記、プログラム内での利用と、ひととおりの局面で多言語対応ができそうです。
拡張機能のテスト
この記事ではpnpm
を使うようにしましたので、下記のようにコマンドを実行します。
pnpm run test
初回の場合、もろもろライブラリやvscodeの実行環境がインストールされます。
しばらく待ちますと、一瞬VSCodeが別ウィンドウで開いたあと、テストが完了すると思います。
プロジェクト作成直後ではテストコードは以下のような内容で、サンプルのものが実装されています。
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});
実際の開発では、ここに必要なテストコードを実装していくことになります。
パッケージングとインストール
手元のVSCodeにインストールしたい場合はとりあえずパッケージングという処理が必要になります。
このパッケージングやマーケットプレイスへの公開にはvsce
というツールを使います。
このツールで作成したvsix
ファイルを使い、手元のVSCodeへインストールすることができます。
が、その前にいくつか必要な作業があります。
vsce
の実行環境を作る
まずはvsce
を実行できる環境を作ります。
vsce
はnpmのモジュールでもあるため、ふつうにnpm install -g
でインストールすることも可能ですが、
ここではvsce
実行用のDockerイメージを作成する方法を紹介します。
公式のドキュメントにしたがって以下のコマンドでDockerイメージをビルドします。
DOCKER_BUILDKIT=1 docker build --tag vsce "https://github.com/microsoft/vscode-vsce.git#main"
ビルドが完了したら、--version
オプションでコンテナの動作確認。うまく動作していれば、vsce
のバージョンが出力されるはずです。
docker run --rm -it vsce --version
拡張機能をパッケージングするには下記のようにpackage
サブコマンドを使いますが、
docker run --rm -it -v "$(pwd)":/workspace vsce package
ここまでの状態でvsce package
を実行すると下記のようなエラーが出力されるはずです。
ERROR It seems the README.md still contains template text. Make sure to edit the README.md file before you package or publish your extension.
このエラーはなんと「README.md がテンプレ文を含んでますよ」という怒られ方です。
package や publish をする前に、README.mdをちゃんと書きなさいよと言われております。
以降でREADMEの編集他、必要な作業を見ていきます。
READMEの編集
取り急ぎ、ということで下記のように編集します。
# sample-extension README
- This is the README for your extension "sample-extension". After writing up a brief description, we recommend including the following sections.
+ この拡張機能は自宅用です。
## Features
(以下略)
個人用途であればこれで十分です。とりあえずパッケージングできるようになります。
docker run --rm -it -v "$(pwd)":/workspace vsce package
公開する予定がある場合には、しかるべき内容をREADMEに記述しましょう。
package.jsonについて
README.mdを編集してパッケージングを実行すると、下記のようなwarningが出力される場合があります。
WARNING A 'repository' field is missing from the 'package.json' manifest file.
Do you want to continue? [y/N]
これは、拡張機能のマニフェストファイルであるpackage.json
の内容が不足していますよ、という警告です。
個人用途であれば特に無視してy
を選択してしまっても問題ありませんが、公開する予定がある場合は内容をある程度埋めておく必要があると思います。
各項目の意味、用途については、下記の公式ドキュメントをご参考ください。
LICENSE ファイルについて
package.json
についての警告を無視すると、次に下記のようなwarningが出力される場合があります。
WARNING LICENSE.md, LICENSE.txt or LICENSE not found
Do you want to continue? [y/N]
これは拡張機能のライセンシングについてのファイルがありませんよ、という警告です。
これも個人用途であれば無視してしまってかまいません。
公開する予定がある場合は、下記のような記事を参考にライセンスについて明記しておくのがよいと思います。
ルールとして、ファイル名はメッセージのとおりLICENSE.md
、LICENSE.txt
、LICENSE(拡張子なし)
のいずれかであればいいようです。
ちなみに、定型のライセンス条文を自動生成してくれるVSCodeの拡張機能もあります。
vsixファイルを使ったインストール
パッケージングが完了すると、vsixファイルが作成されます。
ファイル名はpackage.json
の内容が使われ、{name}-{version}.vsix
のようになります。
vsixファイルからのインストールはアクティビティバーの拡張機能から行います。
作成されたvsixファイルを選択すると拡張機能がインストールできます。
拡張機能の公開
拡張機能の公開までの手順は、以下の公式ドキュメントにまとめられています。ここでは大まかな流れだけを説明します。
- Azure DevOps のアカウントを作る
- Azure の Organization(組織)を作る
- Personal Access Token を発行する
- Visual Studio Code MarketPlace のアカウントを作る
- 作成した publisher name を
package.json
のpublisher
に記載する -
vsce
から publisher name でログインする(3の Personal Access Tokenが必要) -
vsce
でpublishする
1 から 4 の手順がやや面倒ですが、一度アカウントを作成してしまえばあとは繰り返し publish することができます。
この手順以外にも、MarketPlace上で直接vsixファイルをアップロードする方法もあるようです。
また、Azure DevOps で発行する Personal Access Token には有効期限が設定されていて、期限が切れると利用できなくなります。
そういった場合は同様の手順で、 Azure DevOps から新しいトークンを発行してください。
GitHub Actions を使ったCD/CI
最後に、GitHub Actionsを使ったデプロイ、ついでに自動テストについてですが、
私は下記の記事を参考にさせていただきました。ここでは記事の紹介にとどめさせていただきます。(そのままでいけてしまった)
蛇足ですが、弊リポジトリの例も併せて貼っておきます。
CHANGELOG のこと
CHANGELOG.md
は拡張機能の更新履歴です。VSCodeのマーケットプレイスでは、下のように表示されるようになります。
個人用途であればそこまで気にする必要はありませんが、公開する予定のある場合は、リリースごとにこまめに記載するようにした方がいいと思います。
プロジェクト作成直後は以下のような内容になっており、「注目すべきすべての更新はこのファイルに書いておいてね」「このファイルの書き方はリンク先に書いてあるからよく読むことをおすすめするよ」と書いてあります。
# Change Log
All notable changes to the "sample-extension" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release
まとめ
長くなりましたが、以上です。
今回は、「こんなちょっとした機能があったら便利なのにな〜」という思いつきをきっかけにVSCodeの拡張機能を作ってみて、その全体の流れをご紹介しました。
なにか1つでも参考にしていただければ幸いです。
最後まで読んでいただきありがとうございました。
ではまた!
Discussion