【vscode】改めてtypescriptで拡張機能を開発してみる

初書：2021/04/28
mac : 11.2.3
vscode : 1.55.1

# 前書き
前回：[vscodeの拡張機能を作ってみる - Qiita](https://qiita.com/yuu_1st/items/d2d5a18de4859a165260)

まだtypescriptを記述できない頃に試した拡張機能開発
今回再び拡張機能を作ろうと思ったのだが、前回の記事を見様見真似で書いても上手くいかないことがあり、
作成している言語も若干違うし改めてメモっていこうという次第である。

# 前提

node.jsとnpm、yo、generator-codeはインストール済みとする。
（yo、generator-codeは前回の記事でインストールしているので、そちら参照）

# 環境の作成

作成したいフォルダの下で、下記コマンドを実行する。

```zsh
% yo code
```

以降出てくる質問はtypescriptを選択する以外は前回と同じ

# 前回で知らなかったことメモ

この先も基本的には前回の記事で一通り作成できるのだが、それ以外にも気づいたこととかをメモしていく。

## package.jsonのactivationEvents

前回何も考えずにとりあえず置き換える精神で置いた気がするが、これ意外に重要だった。

参考：[Visual Studio Code 拡張機能のアクティベート activationEvents](https://clickan.click/activation-events/)

これは、拡張機能を起動するタイミングを記述するものらしい。
最初から全ての拡張機能を読み込んでいてはvscodeも重たくなるよね、ということで任意に読み込むタイミングを制御できる。

前回はコマンドそのまま記述したが、あれは「そのコマンドが実行されたら拡張機能を読み込む」という意味となる

つまり、
`コマンドパレットからコマンドを実行`→`拡張機能のactivateが呼ばれる`→`コマンドが実行される`
という感じになる。

これは記述次第でタイミングを変更することができ、また複数のタイミングを指定可能。

例えば
`*`だとvscodeが起動した時（あまり使うべきではないが）
`onLanguage:typescript`だと.tsファイルが開いた時
`onCommand:xxxx`だとxxxxコマンドが実行された時
などという意味になる。

まぁ、今回もコマンド実行時に起動させればいい拡張機能なのだが。

## npm run compileとnpm run watch

typescriptは直接実行できないので、javascriptへコンパイルする必要がある。
typescriptを選択すると、上記二つのコマンドがpackage.jsonに追加されるので、特に設定せずとも利用可能。

・・・なのだが、テスト実行するときに勝手にコンパイルが走るので、特に使う機会がない気がする。
あとこれら実行しても一見変化がないのだが、/srcのファイルが/outに出力されていた（tsconfigに書いてある）

## ユーザー情報の保存

よくみるsettings.jsonのやつ。

参考：[VSCodeの拡張機能開発でユーザー設定を変えながら単体テストする - Qiita](https://qiita.com/bucchi49/items/a3454f396f804f353ce2)

設定する場所は`package.json`の`contributes`の中

```json-doc
    "configuration":[
      {
        "title": "package_title", // ユーザー設定を開いた時の左側のリストに出てくる名前
        "properties": {
          "package_title_2.variable" : { // settings.jsonで指定する時の名前
            "type" : "string", // 保存形式
            "default" : "", // デフォルト値
            "description": "description" // 説明。ユーザー設定の項目とかsettings.jsonでカーソル置くと出てくる
          }
        }
      }
    ]
```

（`package_title_2.variable`とそれぞれの値は変更可能。また`package_title`と`package_title_2`は重複可能）

コード内で取り出すときは、

```ts
vscode.workspace.getConfiguration("package_title_2").variable;
```

という感じで取り出せる。variableのところは流石に予測変換してくれないが。

## メッセージを表示し、ついでにボタンも表示する

右下に出てくるあれ。

```ts
vscode.window.showInformationMessage("メッセージの表示");
```

これで表示できる。
エラー表示は、`vscode.window.showErrorMessage`を使えば同様に可能。

更に、メッセージにボタンを追加して表示する場合は、

```ts
vscode.window.showInformationMessage("メッセージの表示","了解").then(e => {
  if(e === "了解"){
    // 動作
  }
});
```

という風にすれば、`了解`というボタンが増え、押すとthen以降が呼ばれる。
その時に引数に押されたボタンの表示内容が入るので、それに応じて分岐する。
（ちなみに×で消してもthenが呼ばれる。その際eにはundefinedが入っている）

ちなみに・・・

自動的に閉じる方法を調べたが存在していないらしい。
自動的に閉じるには、`setStatusBarMessage`を使うしかない模様。(左下のバーに表示されるので正直気付かないが)

[Auto dismiss messages · Issue #2732 · microsoft/vscode](https://github.com/Microsoft/vscode/issues/2732)


## urlを開く

urlからブラウザを開ける方法。

```ts
vscode.env.openExternal(vscode.Uri.parse("url"));
```
ちなみに昔のバージョンに対応する場合は、別のアプローチが必要。

[javascript - How to open browser from Visual Studio Code API - Stack Overflow](https://stackoverflow.com/questions/34205481/how-to-open-browser-from-visual-studio-code-api)

## データストレージに保存

`settings.json`とは別で、内部データを保存しておきたい時に使えるもの。

ワークスペース、グローバルスペースに保存する方法があり、それぞれキーと値の組み合わせで保存するものと、ファイルに書き込む方法がある。

ちなみにここではキーと値の組み合わせである`globalState`しか説明しない（なぜならglobalStateしか使っていないから）が、ファイルに書き込むタイプの一つであるstoragePathが非推奨になっていたので、こちら側は別で試してみる必要がありそう。

とりあえずAPIドキュメントを見てみると、

```
globalState: Memento & {setKeysForSync}

A memento object that stores state independent of the current opened workspace.
```

だそう。使い方とか一切分からないのだが、このAPIドキュメントはどうやって読むのか未だに謎。

ということで他で調べてみる。調べた結果：

保存する場合は`vscode.ExtensionContext.globalState.update(key,value)`、読み込む場合は`vscode.ExtensionContext.globalState.get<string>(key);`を使用するらしい。ちなみに`vscode.ExtensionContext`は`activate`の引数。

```ts
export function activate(context: vscode.ExtensionContext) {
    let savedata = context.globalState.get<number>("loadtime") ?? 0; // 初回読み込みはundefinedになる。保存されていないから。
    savedata++;
    context.globalState.update("loadtime",savedata);
}
```

これは拡張機能がアクティブになった回数を記録するコード。例文なので必要性は考えていない。

[Common Capabilities | Visual Studio Code Extension API](https://code.visualstudio.com/api/extension-capabilities/common-capabilities#data-storage)

# 公開してみる

自分用としてやる分は前回やったので、今回はテスト含めて実際に公開を試みてみる。
その時のメモ

## 使ってないパッケージをアンインストールする(手動)

手動です。package.jsonの"dependencies"欄を確認し、使用していないパッケージがあればアンインストール。
拡張機能はファイルサイズが大きくなることが好ましくなく、vsixファイルを作成するときにファイル数が多いとWARNINGが出るのでやっておく。

## esbuildを使う

圧縮系操作。上のWARNINGで提示してきたので使ってみる。

[Bundling Extensions | Visual Studio Code Extension API](https://code.visualstudio.com/api/working-with-extensions/bundling-extension)

### インストール

まずはインストール

```zsh
% npm i --save-dev esbuild
```

### package.jsonの追加

次にpackage.jsonのscriptに以下を追加（変更）する。元々あったscriptsの中身は消していい。残してもいいけど。

```json
"scripts": {
    "vscode:prepublish": "npm run -S esbuild-base -- --minify",
    "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=dist/main.js --external:vscode --format=cjs --platform=node",
    "esbuild": "npm run -S esbuild-base -- --sourcemap",
    "esbuild-watch": "npm run -S esbuild-base -- --sourcemap --watch",
    "test-compile": "tsc -p ./"
},
```

（outfileが下のリンクと異なるので注意。）
とりあえず`npm run esbuild`を叩いてみると、`import * as xx from "xx";`を使ういくつかでエラーがあった。
tsconfig.jsonに`"esModuleInterop" : true`をつけ、いくつか記述方法を変えればいいらしいが、詳しくは他の方に任せる。

[esModuleInterop オプションの必要性について - Qiita](https://qiita.com/karak/items/29ff148788f5abb15331)

次に`main`の書き換え。出力先が変化したので、こちらもそれに対応する必要がある。

```diff_json
-  "main": "./out/extension.js",
+  "main": "./dist/main.js",
```

### .vscodeignoreの変更

次に、`.vscodeignore`を変更する必要がある。これはパッケージ化する時に除外するファイルを選択する、`.gitignore`のvscode版
今回不要になるのは、`node_modules`と`out/`ので、それぞれ追記する。

### デバッグの修正

パッケージ化はこれで問題ないのだが、ここで問題になるのがデバッグ側。
普段使う▶︎で実行しても上手く行かなくなる。[^1]

[^1]: 初見だと一見上手く行ってるように見えるが、distディレクトリかその中のmain.jsを消すと実行できなくなる。

これは、普段は実行時にコンパイラが走っているのだが、これで生成されたファイル群はoutディレクトリの中に生成され、`package.json`の`main`に指定されたファイルではないからである。
これを一致させる方法の一つとして、実行時に走るコンパイラを`esbuild`に変更する。

まずコマンドパレット(com + shift + P)から、タスクの構成「Tasks : configure task」を選択し、`npm: esbuild`を選択する。
すると、`.vscode/tasks.json`に何かが追加される。

その後、`.vscode/launch.json`を開き、`configurations`の``"name": "Run Extension"`側の`preLaunchTask`を`npm: esbuild`に書き換える。

これで無事にデバッグ側のmain.jsを元にデバッグされるようになる。

## アイコンの登録

拡張機能を公開するなら、やはりアイコンくらいは設定しておきたい。

ということでアイコンを作成する。作成方法は任せる。サイズは128×128のpngである必要があるらしい。

ちなみに自分は以下のサイトを有り難く使わせていただいた。

[アプリアイコン風デカ文字ロゴ- - つぶデコジェネレーターメーカー](https://generator.tubudeco.com/g1606/)

作成したら、ディレクトリに置いておき、package.jsonに以下を追加する。

```json
  "icon" : "icon.png",
```

この際、`アイコンは、HTTPS プロトコルのリポジトリがこの package.json で指定されている必要があります。`という警告が出るのだが、無視しても構わない模様。ただ、若干気になる。

## Azure DevOps Servicesに登録する

拡張機能を公開するには[Azure DevOps Services](https://azure.microsoft.com/ja-jp/services/devops/)に登録する必要がある。
なおログインはマイクロソフトアカウントオンリー[^2]

[^2]: githubアカウントでもログイン出来るような画面だが、メールアドレスが既存のマイクロソフトアカウントと衝突していると登録できないっぽい。

登録できたら、右上の人のアイコン（user settings）から`Personal Access Tokens`を選択

`New Token`を選択し、パーソナルアクセストークンを生成する。

Nameは任意、OrganizationはAllに、Expirationは任意、Scopesはcustomにして、Marketplace(ない場合はShow all scopesを選択)の`Manage`をチェックし、create

これでパーソナルアクセストークンが生成された。

次に[管理ページ](https://marketplace.visualstudio.com/manage)にアクセスし、こちらもアカウント登録。

Nameは公開名、IDはユニークなIDを作成し、他は任意の模様。

アカウントが作成できたら、次はvsceでログインをする。

```zsh
% vsce login <publisher name>
```
※\<publisher name\>はおそらく管理ページで作成したIDに相当すると思われるが、NameとIDを同じにしてしまったためどちらか不明。誰か判明したらコメントください。

`Personal Access Token for publisher`と表示されれば、先ほど取得したパーソナルアクセストークンを入力。

これで準備は完了。ちなみにAzure DevOps Servicesへの登録方法は以下に記載されているものを参考にしている。

[拡張機能の公開| Visual Studio Code Extension API](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#get-a-personal-access-token)

## 公開する

あとは`package.json`の特にversionとか`.vscodeignore`とか`README`とか`CHANGELOG.md`をしっかり書いたかとかを確認して、大丈夫そうなら実際に公開する

```zsh
% vsce publish
```

`DONE  Published xxx`と出れば無事公開完了。反映には少し時間がかかるので少し待ってから確認。（管理ページの方は即反映されている）

## 更新する

公開の時と同じ

```zsh
% vsce publish x.x.x
```

`x.x.x`には設定するバージョンか、もしくは`major`、`minor`、`patch`を指定することも可能。

# 終わりに

他にも見つけたら追加していく予定。（次拡張機能を開発するのはいつになるやら・・・。）

APIが結構ややこしいので作成に手間取るが、意外に楽しい。

ちなみに今回開発したのはこちら→ [【vscode・qiita】vscodeからqiitaに投稿する拡張機能を作ってみた - Qiita](https://qiita.com/yuu_1st/items/a4a4d78b20bb1c649e9b)

# 参考サイト

[Visual Studio Code API コマンド編 -vscode.commands-](https://clickan.click/vscode-command/)

[VSCodeデバッグ前に任意のタスクを実行させる - SE（たぶん）の雑感記](https://hiroronn.hatenablog.jp/entry/20181022/1540166462)

[VSCode拡張機能の開発～リリース(公開)編～｜hatena75｜note](https://note.com/_hatena75/n/n50ced4383b14)

APIドキュメント：[VS Code API | Visual Studio Code Extension API](https://code.visualstudio.com/api/references/vscode-api)