初めに
VSCodeの拡張機能をTypescriptで作ってみました。
実行するとランダムな飯テロ画像を表示してくれる画期的なアプリです。
公開できるほど整理されていないので、身内のみで楽しむ予定です。
開発環境
node:16.17.1
環境構築&開発
環境構築
-
まず開発に必要なパッケージをインストールします。
npm install -g yo generator-code
-
インストールが終わったら、以下のコマンドでプロジェクトを生成しましょう。
yo code
実行すると、以下のことが聞かれます。
->
は私の回答です。$ yo code _-----_ ╭──────────────────────────╮ | | │ Welcome to the Visual │ |--(o)--| │ Studio Code Extension │ `---------´ │ generator! │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y ` ? What type of extension do you want to create? -> New Extension (TypeScript) ? What's the name of your extension? -> test-extension ? What's the identifier of your extension? -> test-extension ? What's the description of your extension? -> ? Initialize a git repository? -> No ? Bundle the source code with webpack? -> Yes ? Which package manager to use? -> npm
プロジェクトが作成されると、VSCodeで開くか聞いてきます。
このようなフォルダ構成が生成されています。環境構築については以上で完了となります。
環境構築後の動作確認
-
動作確認をしてみましょう。実際に処理を書くファイルは
src\extension.ts
です。
日本語に翻訳すると、以下のようなことが書かれています。import * as vscode from "vscode"; // この方法は、拡張機能がアクティブ化されたときに呼び出されます // 拡張機能がアクティブ化されますコマンドが最初に実行されたとき export function activate(context: vscode.ExtensionContext) { // コンソールを使用して診断情報(Console.log)とエラー(Console.Error)を出力します // このコード行は、拡張機能がアクティブ化されたときにのみ実行されます console.log('Congratulations, your extension "test" is now active!'); // コマンドはpackage.jsonファイルで定義されています // 次に、登録コマンドでコマンドの実装を提供します // commandIdパラメーターは、package.jsonのコマンドフィールドに一致する必要があります let disposable = vscode.commands.registerCommand("test.helloWorld", () => { // ここに配置するコードは、コマンドが実行されるたびに実行されます // ユーザーにメッセージボックスを表示します vscode.window.showInformationMessage("Hello World from test!"); }); context.subscriptions.push(disposable); } //この方法は、拡張機能が無効になっているときに呼び出されます export function deactivate() {}
-
試しに
F5
を押下すると、拡張機能を試すことができます。
新しくVSCodeが立ち上がるので、F1
でコマンドパレットを起動し、Hello World
と入力してみましょう
※エラーが出てくると思いますが、無視します。
すると、右下にメッセージが出力されます。
vscode.window.showInformationMessage("Hello World from test!");
の部分が実行されていますね。
これにて環境構築後の動作確認は終了です。
実際に開発をしていく
今回作成するアプリは以下の手順で開発を進めます。
1. showInformationMessage
にてメッセージを表示する
2. Localに飯テロ画像を用意し、出力される対象を選択する
3. 現在開いている別タブにcreateWebviewPanel
にて選択された飯テロを表示する
-
まず
src\extension.ts
のactivate
メソッドを整理し、
コマンドパレットでmeshi-tero
が呼び出された際に起動する関数を用意します。
とりあえずshowInformationMessage
に出力するテキストを入力します。export function activate(context: vscode.ExtensionContext) { let cmd = vscode.commands.registerCommand("meshi-tero", () => { customContext("meshi-tero", context); }); context.subscriptions.push(cmd); } const customContext = (_key: string, context: vscode.ExtensionContext) => { vscode.window.showInformationMessage("飯の時間だ!!!"); };
-
vscode.commands.registerCommand
の内容を変えたのでpackage.json
の内容も変えましょう。
ここで呼び出されるコマンドが定義されています。"activationEvents": [ "onCommand:meshi-tero" ], "main": "./dist/extension.js", "contributes": { "commands": [ { "command": "meshi-tero", "title": "meshi-tero" } ] },
-
飯テロ画像を用意します。
-
では次に表示する画像をランダムに選択し、表示するファイルのURIを生成します。
今回はとりあえず1~27個の画像を用意し、ファイルを番号順に命名したのでgetImage()
の内容でランダムな画像を選択します。フォルダ内の一覧を持ってくるなどしたかったのですが、読み込めませんでした…
方法はあると思いますが今回は諦めて、vscode.Uri.joinPath
を使用しローカルファイルへのURIを生成します。
引数に「拡張機能を含むディレクトリのURI+フォルダパス+フォルダ名」を置き、結合しています。また、
imageFilePath
で指定している"images"
はプロジェクト直下に置いています。
今回作ってわかりましたが、拡張機能の開発はローカルファイルへの参照に癖があります…const getImage = () => { const min = 1; const max = 27; const index = Math.floor(Math.random() * (max + 1 - min)) + min; return `${index}.jpg`; }; const customContext = (context: vscode.ExtensionContext) => { /** 画像ファイル置き場 */ const imageFilePath = "images"; /** メッセージ出力 */ vscode.window.showInformationMessage("飯の時間だ!!!"); /** ファイルパスを生成 */ const onDiskPath = vscode.Uri.joinPath( context.extensionUri, imageFilePath, getImage() ); /** ファイルパスのURIを生成 */ const catGifSrc = panel.webview.asWebviewUri(onDiskPath); };
-
createWebviewPanel
でWebViewのパネルを生成し、表示します。
今回で追加した処理はviewMeshiTeroImage()
とvscode.window.createWebviewPanel
です。
viewMeshiTeroImage()
では表示する内容をHTML形式で記載しています。vscode.window.createWebviewPanel
に関しては公式の解説を見るのが一番かと思います。
extension-guides webview最後に、
panel.webview.html
に読み込めば完成です!/** 画像呼び出し用のHTML */ function viewMeshiTeroImage(path: Uri) { return `<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> </head> <body> <img src=${path} /> </body> </html>`; } const customContext = (context: vscode.ExtensionContext) => { const imageFilePath = "images"; /** メッセージ出力 */ vscode.window.showInformationMessage("飯の時間だ!!!"); /** WebViewのパネルを作成 */ const panel = vscode.window.createWebviewPanel( "meshi", "meshi", vscode.ViewColumn.Beside, // 新しいタブに生成 { enableScripts: true, enableFindWidget: true, localResourceRoots: [ vscode.Uri.joinPath(context.extensionUri, imageFilePath), ], } ); /** ファイルパスを生成 */ const onDiskPath = vscode.Uri.joinPath( context.extensionUri, imageFilePath, getImage() ); /** ファイルパスのURIを生成 */ const catGifSrc = panel.webview.asWebviewUri(onDiskPath); /** 別タブに画像を表示 */ panel.webview.html = viewMeshiTeroImage(catGifSrc); };
最終的にできたコード
import * as vscode from "vscode";
import { Uri } from "vscode";
export function activate(context: vscode.ExtensionContext) {
let cmd = vscode.commands.registerCommand("meshi-tero", () => {
customContext(context);
});
context.subscriptions.push(cmd);
}
/** 画像呼び出し用のHTML */
function viewMeshiTeroImage(path: Uri) {
return `<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<img src=${path} />
</body>
</html>`;
}
/** 表示する画像をランダムに選定 */
const getImage = () => {
const min = 1;
const max = 27;
const index = Math.floor(Math.random() * (max + 1 - min)) + min;
return `${index}.jpg`;
};
/** meshi-tero実行 */
const customContext = (context: vscode.ExtensionContext) => {
const imageFilePath = "images";
/** メッセージ出力 */
vscode.window.showInformationMessage("飯の時間だ!!!");
/** WebViewのパネルを作成 */
const panel = vscode.window.createWebviewPanel(
"meshi",
"meshi",
vscode.ViewColumn.Beside,
{
enableScripts: true,
enableFindWidget: true,
localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, imageFilePath),
],
}
);
/** ファイルパスを生成 */
const onDiskPath = vscode.Uri.joinPath(
context.extensionUri,
imageFilePath,
getImage()
);
/** ファイルパスのURIを生成 */
const catGifSrc = panel.webview.asWebviewUri(onDiskPath);
/** 別タブに画像を表示 */
panel.webview.html = viewMeshiTeroImage(catGifSrc);
};
// 拡張機能無効時発火
export function deactivate() {}
パッケージ作成&導入方法
パッケージ作成
-
ローカルにてインストールできる形式に出力するために以下コマンドを起動します。
npx vsce package
以下のことが聞かれると思います。
- ReadMeが更新されていないよ!
- 適当に更新してあげてください。
- LICENSEに関する文章がないよ!
- 無視して大丈夫です。(yを入力)
- ReadMeが更新されていないよ!
-
プロジェクト直下に
.vsix
ファイルが生成されます。
この生成されたファイルをインストールすることで拡張機能が使えるようになります。
導入&使い方
終わりに
ネタアプリでしたが、思ったより簡単にVSCodeの拡張機能を作成することができました。
ローカルファイルへの参照に癖があり少し工夫が必要ですね。
タイマー起動やファイルSave時の起動にも興味があります、いつかやってみたいです。
また、ReadMe.mdにローカルの画像を使用したかったのですが現在はまだ対応していないようです…
記事の内容に間違いがありましたらご連絡ください!