虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

VSCode の拡張機能を作成する

こんにちは。虎の穴ラボのサカガミです。

この記事は「虎の穴ラボ夏のアドベントカレンダー」20日目の記事です。
19日目は山田さんによる「監視ツールPrometheusのエクスポーターを自作してAWSのコスト監視を試してみました」が投稿されました。
21日目はnsdさんによる「スプリントレトロスペクティブの話~ふりかえってみよう~」が投稿されます。こちらもぜひご覧ください。

はじめに

今回の内容は「VSCode の拡張機能を作成する」です。

先日 Visual Studio Code (以下、VSCode) の拡張機能を公開しましたので、 その作成方法をご紹介致します。

きっかけ

私はときどきブックマークレット (Bookmarklet) を作るのですが、 以前は以下のような流れで作成していました。

  1. VSCode でブックマークレット作成用のフォルダを開く
  2. JavaScript ファイルを作成
  3. コードを書く
  4. コードをブラウザーのコンソールにコピペして動作確認
  5. できあがったら VSCode の拡張機能 Minify で 1 行に圧縮
  6. 1 行になったコードを同拡張機能 Encode Decode で URL エンコード
  7. URL エンコードされたものをコピー
  8. ブックマークとしてブラウザーに登録

この手順の利点は、 開発時に VSCode の支援を受けることができ、 ソースコードもきれいに整形された状態のままで開発し Git で管理可能なことです。

その反面、圧縮とエンコードの部分に少し手間がかかっていました。 Minify は別のファイルとして結果が出力されることや、 毎回 Encode Decode によるエンコード方式を選択する必要があったためです。

この手間を改善するため、VSCode の拡張機能を作成することにしました。 機能としては「コマンドを実行したら開いているエディタのコードを圧縮、 URL エンコードしてクリップボードにコピーする」という簡単なものとなります。

開発環境

本記事では以下の環境で開発しています。

  • Windows 10 Pro 21H2
  • WSL2 + Ubuntu 22.04
  • Docker
  • VSCode の拡張機能 Remote - Containers、Remote - WSL

拡張機能を作成するには npm で yo (Yeoman), generator-code, vsce をインストールする必要があります。 また、vsce の実行には Node.js v14 以降が必要です。

Ubuntu 22.04 において、apt により標準でインストール可能な Node.js は v12 です。 Ubuntu へ直接 v14 以降をインストールしてもよいですが、 今回は Docker と VSCode の拡張機能を使用し開発用のコンテナを作成します。

Docker のインストール

Docker Desktop をインストールするか、以下のサイトを参考に WSL 上の Ubuntu へ Docker CE をインストールします。

blog.ecbeing.tech

開発に使用する拡張機能を VSCode へインストール

開発に使用する以下の 2 つの拡張機能を VSCode へインストールします。

開発用コンテナの準備

  1. 開発用コンテナを作成するため、Ubuntu 上でディレクトリを作成し VSCode で開きます。
    今回は "vsce-bookmarkletify" としました。

    $ mkdir vsce-bookmarkletify
    $ code vsce-bookmarkletify
    
  2. VSCode が起動したら F1 キーか Ctrl + Shift + P キーを押してコマンドパレットを開きます。

  3. コマンドパレットで以下の通り選択します。
    1. Remote-Containers: Add Development Container Configuration Files...
    2. Node.js & TypeScript
    3. 18-bullseye
    4. OK
  4. .devcontainer フォルダの中に設定ファイルが作成されます。
  5. Dockerfile を開きます。
  6. 以下の 2 行をアンコメントし <your-package-list-here>libsecret-1-dev へ書き換えます。
    ※libsecret-1-dev は vsce の実行に必要です。

    # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    #     && apt-get -y install --no-install-recommends <your-package-list-here>
    

    書き換え後:

    RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
        && apt-get -y install --no-install-recommends libsecret-1-dev
    
  7. 以下の行をアンコメントし <your-package-list -here>generator-code vsce yo へ書き換えます。

    # RUN su node -c "npm install -g <your-package-list -here>"
    

    書き換え後:

    RUN su node -c "npm install -g generator-code vsce yo"
    
  8. 必要に応じて、devcontainer.json の extensions へコンテナ内で使いたい拡張機能を追加します。

  9. 再度コマンドパレットを開き Remote-Containers: Reopen in Container を選択します。
  10. VSCode が開き直され、開発用コンテナが起動します。 (初回は起動するまで数分かかります。)
  11. 開発用コンテナが起動したら Ctrl + @ キーを押してターミナルを開きます。
  12. 以下のコマンドで Node.js v18 と vsce、yo がインストールされていることを確認します。

    • node --version
    • vsce --version
    • yo --version

拡張機能の雛形を作成する

準備ができたので拡張機能を作成していきます。 先ほど作成した開発用コンテナ内での作業となります。

  1. ターミナルで yo code を実行します。
  2. 初回実行時には匿名の使用統計を送信するか確認されるため y/n どちらかを入力します。
  3. 質問が表示されますので以下の通り回答します。
    • What type of extension do you want to create?: New Extension (TypeScript) を選択
    • What's the name of your extension?: 作成する拡張機能の名前 (今回は "Bookmarkletify")
    • What's the identifier of your extension?: 拡張機能の識別子 (今回は "bookmarkletify")
    • What's the description of your extension?: 拡張機能の説明
    • Initialize a git repository?: git リポジトリを初期化するか
    • Bundle the source code with webpack?: webpack でバンドルするか
    • Which package manager to use?: npm と yarn どちらのパッケージマネージャーを使用するか (今回は npm を選択)
    • Do you want to open the new folder with Visual Studio Code?: Skip を選択

以上で bookmarkletify ディレクトリが作成され、その中に雛形が作成されたはずです。

ディレクトリが二重になっているため、 ファイルを移動し自動で作成されたディレクトリを削除します。

$ mv bookmarkletify/* .
$ mv -n bookmarkletify/.* .
$ rmdir bookmarkletify

雛形の動作確認

  1. F5 キーを押すと、開発中の拡張機能がインストールされた新しい VSCode のウィンドウが開きます。
  2. コマンドパレットを開き Hello World を選択します。

右下に Hello World from Bookmarkletify! と表示されたら正常に動作しています。 新しく開いた VSCode のウィンドウは一旦閉じてください。

コマンドの作成

先程実行した Hello World コマンドは 2 つのファイルで作られています。 package.json と src/extension.ts です。

package.json:

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

src/extension.ts:

let disposable = vscode.commands.registerCommand('bookmarkletify.helloWorld', () => {
    vscode.window.showInformationMessage('Hello World from Bookmarkletify!');
});

package.json で bookmarkletify.helloWorld というコマンドを定義し、 extension.ts の registerCommand で処理を登録しています。

同じような形式で追加すれば独自のコマンドを作成できますので、以下のように書き換えます。

package.json:

"activationEvents": [
    "onCommand:bookmarkletify.copyToClipboard"
]
"contributes": {
    "commands": [
        {
            "command": "bookmarkletify.copyToClipboard",
            "title": "%command.copyToClipboard%",
            "category": "Bookmarkletify"
        }
    ]
}

src/extension.ts:

let disposable = vscode.commands.registerCommand('bookmarkletify.copyToClipboard', () => {
    // ここにコマンドが実行された際の処理を書く
});

コマンド名となる title は % で囲む形式で記述しています。 これについては次の項で説明します。

コマンド名を日本語化する

先程 title を % で囲んでいたのはコマンド名を日本語化するためです。

日本語化するには package.nls.json と package.nls.ja.json を作成します。 package.nls.json に英語のコマンド名、 package.nls.ja.json に日本語のコマンド名を記述します。

package.nls.json:

{
    "command.copyToClipboard": "Copy to Clipboard"
}

package.nls.ja.json:

{
    "command.copyToClipboard": "クリップボードへコピー"
}

これでコマンド名が日本語で表示されます。

設定項目を追加する

VSCode の設定画面では拡張機能の設定も可能ですが、 この設定項目も package.json で定義します。

今回の拡張機能では、コードの前後に付与する文字列を設定可能にしています。

package.json:

"contributes": {
    "configuration": {
        "title": "Bookmarkletify",
        "properties": {
            "bookmarkletify.protocol": {
                "type": "string",
                "default": "javascript:",
                "description": "%configuration.protocol.description%"
            },
            "bookmarkletify.prefix": {
                "type": "string",
                "default": "(()=>{",
                "description": "%configuration.prefix.description%"
            },
            "bookmarkletify.suffix": {
                "type": "string",
                "default": "})();",
                "description": "%configuration.suffix.description%"
            }
        }
    }
},

description を % で囲んでいるのは 先程と同様に設定項目の説明を日本語化するためです。

package.nls.json と package.nls.ja.json へ以下を追加します。

package.nls.json:

{
    "configuration.protocol.description": "Controls the protocol given before JavaScript.",
    "configuration.prefix.description": "Controls the string given before JavaScript.",
    "configuration.suffix.description": "Controls the string given after JavaScript."
}

package.nls.ja.json:

{
    "configuration.protocol.description": "JavaScript の前に付与するプロトコルを制御します。",
    "configuration.prefix.description": "JavaScript の前に付与する文字列を制御します。",
    "configuration.suffix.description": "JavaScript の後ろに付与する文字列を制御します。"
}

これで設定項目の説明が日本語で表示されます。

プログラム側からは以下のように設定値を取得できます。 config.get の 1 つ目の引数が設定項目名、2 つ目が設定されていなかった場合のデフォルト値です。

const config = vscode.workspace.getConfiguration('bookmarkletify');
protocol = config.get<string>('protocol', 'javascript:');

vscode.workspace.getConfiguration は VSCode の API です。 使用できる API については VS Code API をご参照ください。

開いているエディタの内容を取得する

今回作成する拡張機能は 「コマンドを実行したら開いているエディタのコードを圧縮、 URL エンコードしてクリップボードにコピーする」ものです。 したがって、開いているエディタの内容を取得する必要があります。

現在開いているエディタは vscode.window.activeTextEditor、 その内容は activeTextEditordocument.getText で取得できます。

また、document.languageIdどの言語のファイルかを示す文字列 が入っていますので、JavaScript 以外を除外します。

src/extension.ts:

function getText() {
    const doc = vscode.window.activeTextEditor?.document;
    if (doc == null || !SUPPORTED_FILES.includes(doc.languageId)) {
        return '';
    }

    let text = doc.getText();
    text = text.trim();

    return text;
}

コードの圧縮と URL エンコード

コードの圧縮と URL エンコードは terserminifyencodeURIComponent を呼び出しているだけです。

src/extension.ts:

async function bookmarkletify(input: string) {
    let output = removeProtocol(input);
    const minifyOutput = await minify(output);
    output = minifyOutput.code ?? output;

    return config.protocol + encodeURIComponent(config.prefix + output + config.suffix);
}

クリップボードへコピーする

URL エンコードが完了したら vscode.env.clipboard.writeText('コピーするテキスト'); で文字列をクリップボードへコピーします。

以上で拡張機能の作成は完了です。

作成したコマンドの動作確認

雛形の動作確認時と同様に、 F5 キーを押すと開発中の拡張機能がインストールされた新しい VSCode のウィンドウが開きます。

コマンドパレットからコマンドを選択することで動作確認が可能です。

作成した拡張機能を公開する

作成した拡張機能を公開しインストールしてもらえるようにするには、 Azure DevOps へユーザー登録したあと vsce コマンドで publish する必要があります。

詳しくは Publishing Extensions をご参照ください。

今回作成した拡張機能のダウンロードとソースコード

今回作成した拡張機能は VSCode 内や Visual Studio Marketplace からダウンロード可能です。

marketplace.visualstudio.com

ソースコードは GitHub で公開しています。

github.com

まとめ

以上のように、作り方さえわかれば意外と簡単に拡張機能が作成できます。 皆さんもぜひ作成してみてください。

P.S.

虎の穴ラボでは、私たちと一緒に新しいオタク向けサービスを作る仲間を募集しています。
詳しい採用情報は以下をご覧ください。

yumenosora.co.jp