vscodeの拡張機能を作ってみる

環境
初書：2020/09/10
PC：macOS 10.15.6
node: v12.15.0
npm: v6.13.4
yo: v3.1.1
vsce: v1.79.5

2021/05/12追記：
typescript版作りました→[【vscode】改めてtypescriptで拡張機能を開発してみる - Qiita](https://qiita.com/yuu_1st/items/ea5bf066d165b199b375)

# 前置き

[前回の記事](https://qiita.com/yuu_1st/items/e6061a52aa3b3b2745a6)にて、import文を使おうとしたのだが、minifyを組み合わせる事が出来ない[^1]ことに気付き、拡張機能を使って対応させようと考えた。
目標：minifyする際、importのurlに".min"を追加してminifyする。

ということで"目標"が達成できる拡張機能を自作することにした。（もしかしたら既に世に存在しているかもしれないが。）

[^1]: importする際に、先に.minを付けていると、開発環境時に関数に付けたコメントアウトが見えなくなるが、かといって.minを付けないでいると、export側のファイルはminify出来なくなる。

# 環境の準備
参考サイト：[Visual Studio Code はじめての拡張機能開発](https://qiita.com/rma/items/8c53077d1355ab8fa4c6)
※前提条件として、node.jsとnpmはインストール済みとする。

## ジェネレータのインストール
まずは、ジェネレータをインストールする。インストール済みの場合は飛ばす。

```zsh
% npm install -g yo generator-code
(中略)
npm ERR! code EACCES
npm ERR! syscall access
npm ERR! path /usr/local/lib/node_modules
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, access '/usr/local/lib/node_modules'
```
パーミッションエラーが出た。
[こちらのサイト](https://neos21.hatenablog.com/entry/2019/01/02/080000)を参考に、パーミッションを確認してみると、確かにnode_modulesがroot権限になっていた。
なので、`% sudo chown -R $(whoami) $(npm root -g)`で権限を付与。
（※条件が異なる場合は違う方法が必要かもしれないので、[npmでpermission deniedになった時の対処法[mac]](https://qiita.com/okohs/items/ced3c3de30af1035242d)を参考に対処してください）

改めて、ジェネレータをインストールする。

## 拡張機能の環境の作成
作成したいフォルダの下で、下記コマンドを実行する

```zsh
% yo code
```
これをすると何かいろいろと聞かれるので、作成したいものに合わせて選択する。

```
// どのタイプの拡張機能を作成しますか？ -> jsの拡張機能（typescriptが書けないので、jsにした）
? What type of extension do you want to create? New Extension (JavaScript)
//拡張機能の名前は？ -> 好きな名前
? What's the name of your extension? xxxxxx
//拡張機能のID(識別子)は？ -> これも好きな名前。ただアンダーバーを含む記号は使えないかも
? What's the identifier of your extension? xxxxxx
//拡張機能の説明は？ -> これは空白でも可 
? What's the description of your extension? 
// 「jsconfig.json」でJavaScriptのタイプチェックを有効にしますか？ -> よくわからなかったのでnoにした（調べてない）
? Enable JavaScript type checking in 'jsconfig.json'? No
// gitは生成する？ -> 任意
? Initialize a git repository? Yes
// パッケージマネージャは何にする？ -> 任意。node.jsは軽く触った事があるのでnpmにした
? Which package manager to use? npm
```
なお、参考サイト含め基本的にはtypescriptを使用するのが一般的らしく、jsにすると選択肢が多少異なっている可能性があるので、typescriptを選択する場合の質問は他のサイトを参考にした方がいいかもしれない。

上記の質問が終わったら、ファイルが作成されるので、完了したらそのまま
`To start editing with Visual Studio Code, use the following commands:`
のしたの行にある2つのコマンドを並列に実行する。

なお、この際`code .`が`zsh: command not found: code`になる場合は、vscodeのコマンドパレット（command + shift + p)から、`Shell Command: Install 'code' command in PATH`を実行する必要がある。

# 拡張機能の作成
※`New Extension (JavaScript)`を選択した場合の説明です。typescriptの場合はファイルが若干異なります。

まずは`vsc-extension-quickstart.md`に作成方法などの話が書いてあるので確認する。（ただし英語。google翻訳でかなり綺麗に読める）
基本的に使うファイルは、`package.json`及び`extension.js`の２つ。
また、コマンドパレットから実行するだけの場合は、`extension.js`の中の`function activate`の中にある`let disposable = vscode.commands.registerCommand`の第二引数にある関数内に記述することになる。

ということで、作成していく。

## コードの記述
vscode.commands.registerCommandの第二引数の関数の中に記述していく。

```js:extension.js
// アクティブなエディタを取得
let editor = vscode.window.activeTextEditor;
if (editor) {
	let doc = editor.document;
	// スタート位置（先頭）を取得
	let startPos = new vscode.Position(0, 0);
	// 終了位置（最終行の10000列目）を取得
	let endPos = new vscode.Position(doc.lineCount - 1, 10000);
	// ポジションを選択する
	let cur_selection = new vscode.Selection(startPos, endPos);
	// 全体を取得
	let text = doc.getText(cur_selection);
	let rows = text.split(/\r\n|\r|\n/);
	let co = 0;
	for(let i = 0; i < rows.length; i++){
		if(/^\s*import\s/.test(rows[i])){
			co = rows[i].search(/.js";/);
			rows[i] = rows[i].slice(0, co) + ".min" + rows[i].slice(co);
		}
	}
	let text2 = rows.join('\n');
	console.log("cur_selection : " + cur_selection);
	console.log("text : " + text);
	// 全体を置き換え
	editor.edit(edit => {
		edit.replace(cur_selection, text2);
	}).then(() => {
		vscode.commands.executeCommand("HookyQR.minify").then(() =>{
			editor.edit(edit => {
				edit.replace(cur_selection, text);
			}).then(() => {
				// Display a message box to the user
				vscode.window.showInformationMessage('Minify complete!');
			});
		});
	});
}
```
ソースコード参考：[【拡張機能の作り方】Visual Studio Code（VSCode）のスニペット作成が面倒だったので、自作の拡張機能をインストールした。](https://qiita.com/sfp_waterwalker/items/b2cbdbe1119ea46ab25e)

まぁあまり綺麗とは言えないコードになったが、自分用なので。。
<details><summary>動作の説明</summary>
アクティブファイルの全テキストをtextに入れ、各行に分割。
一行毎にチェックしていき、先頭が`import`で始まる場合は、.js";の前に.minを付ける`.min.js`
つまり、`import {xx} from "./a.js"`を`import {xx} from "./a.min.js"`にする。
その後、minifyを行い、先程追加した.minを消す（元に戻す）。
なお、[このMinifyの拡張機能](https://marketplace.visualstudio.com/items?itemName=HookyQR.minify)を入れている事が前提。検索でよく出てくる[JS & CSS Minifer](https://marketplace.visualstudio.com/items?itemName=olback.es6-css-minify)はminifyするコマンドを見つける事ができず、断念
</details>
なお、vscodeを操作するAPIは、`node_modules/@types/vscode/index.d.ts`もしくは[こちらのサイト](https://code.visualstudio.com/api/references/vscode-api)に記載されている。全て英語だが。

## コマンドの設定

このままだと実行コマンドが"Hello World"になるので、その辺を変えていく。
まず、`vscode.commands.registerCommand`の第一引数の箇所を、`xxxxxx.changeandminify`などの名前に書き換える。
次に、package.jsonを開き、`activationEvents`と、`contributes\commands\command`を上と同じ名前にする。
その後、`contributes\commands\title`に、コマンドパレットで表示したいタイトルに変更する。
また、`publisher`という項目を追加し、作成者の名前を追加する。

## テストする

コードが動くか実験する。F5キーもしくはTouch barがある場合は▶︎を押すと、もう一つvscodeが立ち上がる。
その後、コマンドパレットに、`contributes\commands\title`で設定した名前を実行させると、コードが動くはず。
適切に動けば完了。元のvscodeの■で終了させる。動かなければデバッグ。

# vscodeに取り込む

今回は公開配布の予定は無いので、自分の環境だけで動かせるようにしていく。
先程作成した拡張機能をVSIXファイルにまとめる事で、使用できるようになる。

## VSIXファイルを作成するための準備
yoとかvscode自体がこの機能を持っていて欲しい気がするが、残念ながら無い模様。
そのため、Visual Studio Code Extension Managerというものをインストールする。

```zsh
% npm install -g vsce
```
バージョン情報がどんどん長くなっていく。。（書きながら増やしてる）
`% vsce -V`（小文字vではなく大文字V）でバージョン情報が出たらインストール完了

## VSIXファイルを作成
```zsh
% vsce package
 ERROR  Missing publisher name. Learn more: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions
```
パブリッシャー名を入れないといけなかったらしい。追加して再度実行。
（※コマンドの設定の欄にも記述したため、その時に追加している場合はこのエラーは出ない）

```zsh
% vsce package
 ERROR  Make sure to edit the README.md file before you package or publish your extension.
```
README.mdも更新する必要があるらしい。
package.jsonと同じ階層にあるREADME.mdを変更する。
そして再度実行する。

```zsh
% vsce package
WARNING  A 'repository' field is missing from the 'package.json' manifest file.
Do you want to continue? [y/N] y
 DONE (略)
```
`repository`という項目が必要らしい。ただこれはスキップできるようなので、スキップ。
これでVSIXファイルの作成に成功した。

## vscodeに取り込む
vscodeを起動し、拡張機能（com + shift + X)を開ける。右上の三点リーダーから`VSIXから取り込む`を選択
先程のDONEの後ろにファイルの場所が記載しているので、そこのファイルを選択し、インストール。
インストールが完了しました。と出たら無事完了。

# まとめ
理解出来たら結構簡単に作成できる（多分）ものの、理解するのにかなりの時間がかかった。


# 参考サイト
[Visual Studio Code API コマンド編 -vscode.commands-](https://clickan.click/vscode-command/)
[Visual Studio Code 最小構成の拡張機能をJavaScriptで作成](https://ao-system.net/note/65)
\-本文中に記載した参考先\-
[Visual Studio Code はじめての拡張機能開発](https://qiita.com/rma/items/8c53077d1355ab8fa4c6)
[npm のグローバルインストールに失敗したら](https://neos21.hatenablog.com/entry/2019/01/02/080000)
[npmでpermission deniedになった時の対処法[mac]](https://qiita.com/okohs/items/ced3c3de30af1035242d)
[【拡張機能の作り方】Visual Studio Code（VSCode）のスニペット作成が面倒だったので、自作の拡張機能をインストールした。](https://qiita.com/sfp_waterwalker/items/b2cbdbe1119ea46ab25e)