VS Code Conference Japan 2022 - 2023
2023/01/21 nikkie 14:35-
㊗️ハイブリッド開催🎉
15分2本発表の機会をありがとうございます
2021開催がなければ、この発表は実現していません!
過去のVS Code Conferenceのハンズオンテキストの内容を改造し、その拡張機能を公開するまでのありのまま(成功も失敗も両方)の記録である。
イベントサイト より
VS Code拡張開発の経験がない方(過去の私 含む)向け
開発〜公開までの流れ・全体感を共有
「拡張開発やってみよう」と思っていただけたら🙌
それは2022年9月のこと
作るために必要な 情報には見当 がついていた
「うまくいけば作れるんじゃないか」と拡張開発に体当たり
https://marketplace.visualstudio.com/items?itemName=everlasting-diary.tokimeki-editing
砕けなかった🙌
VS Code 1.74.3
Node.js v16.14.2
npm 8.5.0
"@vscode/vsce": "^2.16.0",
"generator-code": "^1.7.2",
"yo": "^4.3.1"
テキストをベースに写経 & 改造(メインパート)
拡張のE2Eテストを書く
拡張を公開
テキストをベースに写経 & 改造 (メインパート)
拡張のE2Eテストを書く
拡張を公開
VSCode Conference Japan 2021 (2021/11) ハンズオン
VS Codeの拡張機能を作ろう❗ vscodejp/handson-hello-vscode-extension
LSP(Language Server Protocol)を使った拡張を作る
Hello World(簡単な拡張 を作って起動する)
コードレンズ のボタンから ドキュメントを編集 する拡張を作る
経験のない試みなので、ハードルを下げた(これでもできるか分からなかった)
beginner向けテキスト を改造する(変更なし)
英語のドキュメントにあたったが、拡張開発までカバーした書籍(参考文献参照)より早道(日本語で多くの情報を得る)
写経:Hello Worldを動かす
写経:ドキュメントを編集する拡張を動かす
ドキュメントの編集を自分がやりたい編集の仕方に改造する
写経:Hello Worldを動かす
写経:ドキュメントを編集する拡張を動かす
ドキュメントの編集を自分がやりたい編集の仕方に改造する
開発環境構築
ツールのセットアップ
scaffold(拡張開発に必要なファイル一式の生成)
開発中の拡張を動かす
Node.js のLTSバージョン
ハンズオンテキストでは16.13.0
VS Codeに ESLint拡張機能
拡張開発中のバグ混入を防ぐ
Yeoman scaffold=足場(テンプレートに沿ったファイル群)を作ってくれるツール
YeomanのVS Code拡張向けテンプレート
ref: 事前準備 必要な開発環境を整えよう
hello-vscode/
├── .vscode/
├── src/
│ └── extension.ts
├── package.json
└── package-lock.json
上記は抜粋版。 Hello Worldを作成しよう に詳細な説明あります
開発環境構築✅
開発中の拡張を動かす
yo code しただけ。 実装は不要
VS Codeで F5 で拡張をビルド(新しいVS CodeのWindow)
コマンド パレット(F1)を開いて
「Hello World」コマンドを呼び出す
src/extension.ts
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('hello-vscode.helloWorld', () => {
vscode.window.showInformationMessage('Hello World from Hello VSCode!');
});
export function deactivate() {}
showInformationMessage
でVS CodeのWindow右下にメッセージが出た
ソースを編集 したら Shift + Command + F5 でRestart
コマンドパレットから「Hello World」コマンドを呼び出すと、メッセージが変わっている!
「拡張開発できている!!」🙌
package.json
src/extension.ts
ユーザが使えるコマンドの秘密は、この2つのファイル
package.json
にコマンドのID 🏃♂️== skiphello-vscode.helloWorld
という識別子のコマンドを宣言
"contributes": {
"commands": [
{
"command": "hello-vscode.helloWorld",
"title": "Hello World"
}
]
},
src/extension.ts
にコマンドの実装 🏃♂️commands.registerCommand
で hello-vscode.helloWorld
コマンドを実装と紐付けた
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand('hello-vscode.helloWorld', () => {
vscode.window.showInformationMessage('Hello World from Hello VSCode!');
});
export function deactivate() {}
事前準備 必要な開発環境を整えよう に沿って環境構築
Yeomanでscaffold(拡張開発に必要なファイル一式を作成)
Hello World拡張を動かせている!
写経:Hello Worldを動かす
写経:ドキュメントを編集する拡張を動かす
ドキュメントの編集を自分がやりたい編集の仕方に改造する
マークダウンファイル
見出しに日付を挿入 したい
## VSCodeConJP
## 2023/01/21 VSCodeConJP
1つ以上の #
を「同じ数の #
とその日の日付」で 置き換え る
例 ## VSCodeConJP
##(空白)
➡️ ## 2023/01/21(空白)
- ## VSCodeConJP
+ ## 2023/01/21 VSCodeConJP
CodeLens - Show Actionable Context Information Within Source Code
ソースコード中に表示される、ユーザがアクションできるリンク
add date がCodeLens。ユーザはクリックできる
クリックすると日付挿入: ##(空白)
を ## 2023/01/21(空白)
に置き換え
CodeLens
コマンド
ユーザがCodeLensをクリックしたら、コマンドが呼び出され、日付挿入される
今回はマークダウンファイル中にCodeLensを設定する
vscode.CodeLensProvider
実装して独自の CodeLensProvider
クラスを定義
CodeLensProvider
で複数のCodeLensを作るCodeLens1つ1つは「何行目の何文字目から何文字目まで」(range)に作られる
rangeを求めるのに正規表現を使う(後述)
/^#+\s/g
と一致するrange(すべて)にCodeLensを作成
例:1行目が ## VSCodeConJP
##(空白)
にCodeLensを作る(add date というリンク)
codeLenses.push(
new vscode.CodeLens(range, {
title: "add date",
tooltip: "add date",
command: "markdown-date.addDate",
arguments: [range],
})
);
return codeLenses;
markdown-date.addDate
コマンドcommands.registerCommand("markdown-date.addDate", (range: Range) => {
if (vscode.window.activeTextEditor) {
// ## VSCodeConJP の場合、textが「## 」
const text = vscode.window.activeTextEditor.document.getText(range);
const today = dayjs().format("YYYY-MM-DD");
vscode.window.activeTextEditor.edit((editBuilder) => {
// ## VSCodeConJP の場合、「## 」を表すrangeを「## 2023/01/21 」で置き換える
editBuilder.replace(range, text + today + " ");
});
}
});
Markdown を開いたときに拡張機能が起動するようにする
拡張機能が起動したときに CodeLensProvider を起動する
拡張機能が起動するタイミングを、対象の言語 ID のファイルを開いたときにまで遅らせる
"activationEvents": [
"onLanguage:markdown"
],
export function activate(context: ExtensionContext) {
const codelensProvider = new CodelensProvider();
let disposable = languages.registerCodeLensProvider(
{ language: "markdown" },
codelensProvider
);
disposables.push(disposable);
}
1行目の見出しに日付を挿入できるようにしたい
CodeLensと、CodeLensクリックでコマンド呼び出し
vscode.CodeLensProvider
を実装(正規表現にマッチしたrangeに CodeLens
を作る)
Hello Worldコマンド(ユーザが コマンドパレット から呼び出せる)
CodeLensでマークダウン編集(コマンド を呼び出して編集を実現)
写経:Hello Worldを動かす
写経:ドキュメントを編集する拡張を動かす
ドキュメントの編集を自分がやりたい編集の仕方に改造する
## VSCodeConJP
## 2023/01/21 VSCodeConJP
##(空白)
の部分にCodeLensが作成される
## 2023/01/21 2023/01/21 VSCodeConJP
見出しに限らず、特定の文字列の後ろにemojiを挿入できるのでは!?
文字列「歩夢」の後ろに「🎀」を挿入(「歩夢」を「歩夢🎀」で置換)
歩夢 ➡️ 歩夢🎀 ➡️ 歩夢🎀🎀 ➡️ 歩夢🎀🎀🎀 🤩
公開までの手順は続くパートで
vscodejp/handson-hello-vscode-extension beginner向けをベースに、マークダウンを編集する拡張を写経・改造
VS Codeの概念:「コマンド」「CodeLens」
CodeLensからコマンドを呼び出し、CodeLensが設定されている範囲を編集 できる
テキストをベースに写経 & 改造(メインパート)
拡張のE2Eテストを書く
拡張を公開
改造がうまくいき、公開が見え てきた✨
IMO「自分が作ったもので、自分以外が使う可能性があるなら、テストを書きたい」
この発表ではテスト= End to End テスト
Yeomanにより src/test
以下にテストに使うファイルが生成されています
src/
├── test/
│ ├── suite/
│ │ ├── extension.test.ts
│ │ └── index.ts
│ └── runTest.ts
└── extension.ts
npm run test
yarn test
ref: Testing Extensions の「Overview」
Downloading VS Code [==============================] 100%
開発環境(macOS)で、テストの2回めが実行できなかったのに対処
async function main() {
// ... 省略 ...
await runTests(
{
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: ['--user-data-dir', `${tmpdir()}`]
});
// ... 省略 ...
}
マークダウンファイルに対するCodeLensの 設定 (個数や行)
ユーザがCodeLensをクリックしたのをエミュレートした コマンド実行
🅰️テストに使うマークダウンファイル(フィクスチャ)
🅱️テストコードでCodeLensを取得する
フィクスチャを配置
フィクスチャを使うように設定(src/test/runTest.ts
)
.
├── src/
├── test-fixtures/
│ └── markdown/
│ └── example.md
├── package.json
└── package-lock.json
# Test of tokimeki-editing
## テスト歩夢
歩夢の行にemojiを追加できる
この行には何もしません
async function main() {
// ... 省略 ...
const testWorkspace = path.resolve(__dirname, '../../test-fixtures');
await runTests(
{
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [testWorkspace, '--user-data-dir', `${tmpdir()}`]
});
// ... 省略 ...
}
const testFileLocation = '/markdown/example.md';
suite('Extension Test Suite', () => {
let fileUri: vscode.Uri;
let editor: vscode.TextEditor;
setup(async () => {
fileUri = vscode.Uri.file(vscode.workspace.workspaceFolders![0].uri.fsPath + testFileLocation);
const document = await vscode.workspace.openTextDocument(fileUri);
editor = await vscode.window.showTextDocument(document);
});
});
vscode.commands.executeCommand<readonly vscode.CodeLens[]>('vscode.executeCodeLensProvider', fileUri, 100);
const codeLenses = await vscode.commands.executeCommand<readonly vscode.CodeLens[]>('vscode.executeCodeLensProvider', fileUri, 100);
// フィクスチャのファイルでCodeLensはいくつ設定されるか - 2個
assert.strictEqual(codeLenses?.length, 2);
// フィクスチャのファイルでCodeLensは何行目に設定されるか - 2行目と4行目(歩夢がある行)
assert.strictEqual(codeLenses?.[0].range.start.line, 2);
assert.strictEqual(codeLenses?.[1].range.start.line, 4);
ユーザがCodeLensをクリックしたのをエミュレートする意図
test('Insert 🎀 after 歩夢', async () => {
const COMMAND_NAME = "tokimeki-editing.addOshiEmoji";
await sleep(1500); // sleepはユーザ定義関数でブロッキングする(VS CodeのWindowのロード待ちの意図)
// 4行目「歩夢の行にemojiを追加できる」の「歩夢」にCodeLensが設定されている。
// このCodeLensがユーザにクリックされたときに呼び出されるのと同様のコマンド呼び出し(「歩夢」のrangeも渡す)
await vscode.commands.executeCommand(COMMAND_NAME, new vscode.Range(new vscode.Position(4, 0), new vscode.Position(4, 2)));
await sleep(500);
const actual = editor.document.lineAt(4).text;
assert.strictEqual(actual, '歩夢🎀の行にemojiを追加できる');
});
export function run(): Promise<void> {
const mocha = new Mocha({
ui: 'tdd',
color: true,
timeout: 5000 // 2000msから伸ばした(60000msまで伸ばしてもよいかも)
});
// ... 省略 ...
}
Mochaのtimeout設定 を変更
マークダウンを編集できるCodeLensを提供する拡張について、E2Eテストを書いた
CodeLensの 設定 を検証
ユーザがCodeLensをクリックして呼び出される コマンド を実行して検証
runTestsの launchArgs
・Mochaの設定
テキストをベースに写経 & 改造(メインパート)
拡張のE2Eテストを書く
拡張を公開
ブラウザからアップロードして公開
コマンドラインで操作して公開
ドキュメント Publishing Extensions を参照
Visual Studio Marketplace publisher management page
vsce コマンド
マイクロソフトアカウントでサインイン(GitHubアカウントは未検証)
Create publisher(publisherを作る)
npm install @vscode/vsce
package.json
を編集{
// nameはMarketplaceで一意になるように変えるのがオススメ(チュートリアルのままだとかぶってしまう)
"name": "tokimeki-editing",
// publisher management pageで作ったpublisherのIDを書く
"publisher": "everlasting-diary",
// ... 省略 ....
}
拡張のソースコードを vsix
ファイルにまとめる
tokimeki-editing-0.0.1.vsix
🙌
Yeomanでscaffoldした README.md
のままだとpackageでエラー。編集する(内容を削った)
publisherにvsixファイルをアップロード
ドキュメントの「Publish an extension」 に画像あり(次スライド)
アップロード後、verifyされるまで少し待つ
パスしたらメールが来た📧 Extension publish on Visual Studio Marketplace
公開後、拡張を右クリックして表示されるメニューから、非公開にも切り替えられる
vsce package
vsce login <publisher name>
vsce publish
$ vsce login everlasting-diary
Personal Access Token for publisher 'everlasting-diary':
Azure DevOpsの組織(organization)を作る
Azure DevOpsの組織でPersonal Access Tokenを作る
https://go.microsoft.com/fwlink/?LinkId=307137 からサインイン
Visual Studio Marketplace publisher management pageと 同じ マイクロソフトアカウント
組織(organization)だけを作る
画像つきの手順: 組織の作成
User settings(右上のアイコン) > Personal access tokens
トークンの名前
everlasting-diary
MarketplaceのManageだけ
ドキュメントの「Get a Personal Access Token」 に画像あり(次スライド)
vsce package
vsce login <publisher name>
vsce publish
ブラウザからアップロードしてMarketplaceに公開:初めて の方🔰にオススメ
publisher management pageの操作 & vsce コマンド
コマンドラインで操作して公開: 慣れてきたら こちらを
Azure DevOpsでPersonal Access Tokenを作る
GitHub ActionsなどCIツール上で、Marketplaceに公開するためのコマンドを実行
テキストをベースに写経 & 改造
拡張のE2Eテストを書く
拡張を公開
Yeoman によるscaffold
CodeLens で編集
正規表現にマッチする箇所に作成
コマンド 呼び出し
フィクスチャのファイルに対して以下を検証
CodeLensの 設定
CodeLensから実行される コマンド を直接実行した結果
はじめは手動アップロード がオススメ
慣れたら vsce コマンドを
Enjoy extension! 🎀🌈
『Visual Studio Code実践ガイド』74thさん
補足
JavaScriptとTypeScriptはPythonと型ヒントの関係と理解
ref: 『プロを目指す人のためのTypeScript入門』
JavaScriptもあまり経験はありません
npm install に -g
(グローバルにインストール)は意図して付けていません
Pythonで仮想環境を使った経験をベースにしていますが、言語が違うので適切なやり方ではないかもしれません
一案:ハンズオンテキストの本編(LSP編)へ
「VS Codeを拡張できるAPIが用意された(限られた)箇所」 Visual Studio Code実践ガイド (第13章)
拡張開発でどのAPIも利用できるわけではない(テストコードからCodeLens操作できないのもこの方針だからかも)