Visual Studio Codeの拡張機能でWebview APIを使ってHello Worldやってみた

最近、Visual Studio Codeの拡張機能を使って何か作ってみたくなったので、基本の「き」から学び直しつつ、備忘も兼ねてあれこれ整理していきます。
最終的にはエンジニアの鳶道具的に使えるツールとして、普段できるようなものが作れるといいなと思っています。

# 環境

* Macbook Pro(16-inch, 2021, 32GB) / Apple Silicon Chip
* Visual Studio Code(バージョン: 1.74.3)
* node(v18.12.1)
* npm(8.19.2)
* yo(4.3.1)

# ゴール

![スクリーンショット 2023-01-29 17.28.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79238/5d9b9772-2548-d38e-98ab-c53c67553264.png)

# 準備

## Yoコマンドのインストール
次のコマンドを実行して`yo`コマンドを使用できるようにします。

```shell
npm install -g yo generator-code
```

インストールが完了したら、次のコマンドを実行してヘルプが表示されるか確認してみます。

```shell
yo --help
```

<details>
<summary>実行結果</summary>

ヘルプコマンドを実行したらこのような内容が表示されます。
```shell
Usage: yo GENERATOR [args] [options]

General options:
  --help         # Print this info and generator's options and usage
  -f, --force    # Overwrite files that already exist
  --version      # Print version
  --no-color     # Disable colors
  --generators   # Print available generators
  --local-only   # Disable lookup of globally-installed generators

Install a generator:

  Generators can be installed through npm.

  $ npm install generator-angular
  $ yo angular --help

Run local generators:

  Additionally, you can also run local generators without installing via npm.

  $ yo ./path/to/some/generator

Completion:

  To enable shell completion for the yo command, try running

  $ yo completion

Troubleshooting:

  For any issues, try running

  $ yo doctor

Full Documentation: http://yeoman.io

Available Generators:

  code
```
</details>

## 初期プロジェクトの作成

`yo`コマンドが実行できることを確認したら、Visual Studio Codeで拡張機能を開発するためのプロジェクトを作成していきます。

```shell
yo code
```

<details>
<summary>実行結果</summary>

実際の実行内容はこのようになります。

```shell

     _-----_     ╭──────────────────────────╮
    |       |    │   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? Hello World
? What's the identifier of your extension? hello-world
? What's the description of your extension? 
? Initialize a git repository? Yes
? Bundle the source code with webpack? No
? Which package manager to use? npm

Writing in /Users/(省略)/Documents/privates/hello-world...
   create hello-world/.vscode/extensions.json
   create hello-world/.vscode/launch.json
   create hello-world/.vscode/settings.json
   create hello-world/.vscode/tasks.json
   create hello-world/package.json
   create hello-world/tsconfig.json
   create hello-world/.vscodeignore
   create hello-world/vsc-extension-quickstart.md
   create hello-world/.gitignore
   create hello-world/README.md
   create hello-world/CHANGELOG.md
   create hello-world/src/extension.ts
   create hello-world/src/test/runTest.ts
   create hello-world/src/test/suite/extension.test.ts
   create hello-world/src/test/suite/index.ts
   create hello-world/.eslintrc.json

Changes to package.json were detected.

Running npm install for you to install the required dependencies.

added 212 packages, and audited 213 packages in 14s

46 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Your extension hello-world has been created!

To start editing with Visual Studio Code, use the following commands:

     code hello-world

Open vsc-extension-quickstart.md inside the new extension for further instructions
on how to modify, test and publish your extension.

For more information, also visit http://code.visualstudio.com and follow us @code.


? Do you want to open the new folder with Visual Studio Code? Open with `code`
```
</details>

コマンドを実行したらいくつか質問されますが、回答例として記載していま。

```shell
? What type of extension do you want to create? (Use arrow keys)
```

どのようなものを開発するかの質問です。今回は、TypeScriptで拡張機能開発を行なっていくので、`New Extension (TypeScript)`を選択。

```shell
? What's the name of your extension? ()
```
プロジェクト名は何にするかの質問です。今回は、`Hello World`と入力します。

```shell
What's the identifier of your extension?
```
Visual Studio Code内で使用する拡張機能IDを何にするかの質問です。
今回は公開する予定がないので、デフォルトで表示されている内容から変更していません。

```shell
What's the description of your extension? ()
```
拡張機能の説明を入力するかどうかの質問です。今回は入力をスキップします。

```shell
Initialize a git repository? (Y/n)
```
Gitの初期化をするかどうかの質問です。プロジェクト内で`git init`コマンドを実行することと同じような内容です。

```shell
Bundle the source code with webpack? (y/N)
```
モジュールのバンドルをする場合には必要ですが、今回はシンプルな動作確認のみのため、`N`を選択。

```shell
Which package manager to use?
```
使用するパッケージマネージャーを何にするかの質問です。今回は`npm`を選択。

```shell
? Do you want to open the new folder with Visual Studio Code?
```
作成したプロジェクトを`code`コードコマンドを使用して表示するかどうの質問です。`Open with 'code'`を選択。

## 実行

Visual Studio Codeでプロジェクトが表示されたら、動作確認として実行してみます。
1. `F5`キーを押下して実行する
1. 実行後は、新しいウィンドウでVisual Studio Codeが表示
1. コマンドパレットを開く(`⌘Command + ⇧Shift + P`)
1. `Hello World`と入力して検索
1. 表示されたコマンドを実行

![スクリーンショット 2023-01-29 17.31.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79238/7278ea45-f5d9-ae1f-c1da-4b803ae255cb.png)

## コード解説

<details>
<summary>実際のコード</summary>

```typescript:src/extension.ts
import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {

	console.log('Congratulations, your extension "hello-world" is now active!');

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

	context.subscriptions.push(disposable);
}

// This method is called when your extension is deactivated
export function deactivate() {}
```
</details>

```typescript
import * as vscode from 'vscode';
```

Visual Studio Codeの拡張APIを読み込みます。

https://www.npmjs.com/package/@types/vscode

```typescript
export function activate(context: vscode.ExtensionContext) { ... }
```
拡張機能がアクティブになった時にVisual Studio Codeから1度だけ呼び出されます。
`activationEvents`で任意のタイミングで実行させるように設定することもできます。

https://code.visualstudio.com/api/references/activation-events

```typescript
let disposable = vscode.commands.registerCommand('hello-world.helloWorld', () => { ... });
context.subscriptions.push(disposable);
```
Visual Studio　Codeにコマンドを登録します。登録する時は`subscriptions`に追加する必要があります。

第1引数には、コマンドIDを記述します。

https://code.visualstudio.com/api/extension-guides/command#creating-new-commands

第2引数には、実際にコマンドが実行された場合の処理を記述します。

```typescript
vscode.window.showInformationMessage('Hello World from Hello World!');
```
Visual Studio Codeの右下で表示される通知メッセージを設定します。
今回は、Infoレベルでメッセージを表示します。他にもエラーや警告など様々な種類がサポートされています。

https://code.visualstudio.com/api/references/vscode-api

```typescript
export function deactivate() {}
```
他から参照できるように関数を公開します

# Webviewを作成

Webviewを作成するためには、`vscode.window.createWebviewPanel()`を使用します。

<details>
<summary>実際のコード</summary>

```diff_typescript
import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {

	console.log('Congratulations, your extension "hello-world" is now active!');

	let disposable = vscode.commands.registerCommand('hello-world.helloWorld', () => {
- 		vscode.window.showInformationMessage('Hello World from Hello World!');
+ 
+       const panel = vscode.window.createWebviewPanel(
+           'helloWorld',
+           'Hello World',
+           vscode.ViewColumn.One,
+           {}
+       );
+ 
+       panel.webview.html = getWebviewContent();
	});

	context.subscriptions.push(disposable);
}
+ 
+ function getWebviewContent() {
+     return `<!DOCTYPE html>
+ <html lang="ja">
+ <head>
+     <meta charset="UTF-8">
+     <meta name="viewport" content="width=device-width, initial-scale=1.0">
+     <title>Hello World</title>
+ </head>
+ <body>
+     <h1>Hello World!</h1>
+ </body>
+ </html>`;
+ }

// This method is called when your extension is deactivated
export function deactivate() {}
```
</details>

```typescript
const panel = vscode.window.createWebviewPanel(
  'helloWorld',
  'Hello World',
  vscode.ViewColumn.One,
  {}
);
```

第1引数は、Webviewのパネルを識別するためのIDを記載します。
第2引数は、Webviewのタイトルを記載します。
第3引数は、Webviewを表示する場所を指定します。`vscode.ViewColumn.One`は、1番目のエディタに表示されます。左右分割であれば、左側。上下左右の4分割であれば左上になります。

https://code.visualstudio.com/api/extension-guides/webview

```typescript
panel.webview.html = getWebviewContent();
```
実際に表示するHTMLを指定します。　今回は、関数に切り出しているため、`getWebviewContent()`を代入しています。

```typescript
function getWebviewContent() {
    return `<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello World</title>
</head>
<body>
    <h1>Hello World!</h1>
</body>
</html>`;
}
```
関数は、実際のHTMLそのまま記載する形です。

## 実行

最初に実行した手順と同じ

![スクリーンショット 2023-01-29 17.28.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79238/5d9b9772-2548-d38e-98ab-c53c67553264.png)

# 参考

https://code.visualstudio.com/api/get-started/your-first-extension
https://github.com/yeoman/yo