Deno+HTMLで手軽にGUIアプリを作る

## はじめに

[carol](https://github.com/uki00a/carol)という、DenoとHTMLを使ってGUIアプリを作るためのモジュールを作成しました。
この記事では、その紹介も兼ねてcarolを使ったGUIアプリの作成方法について解説します。

## 前提条件

以下の環境で動作確認しています。

- Deno v1.0.3
- Google Chrome v83.0.4103.61

carolはUIを提供するために、ローカルにインストールされたGoogle Chromeを使用します。
そのため、事前にChromeをインストールしておく必要があります。

## Hello World!

まず、以下のようなスクリプトを用意します。

```typescript:mod.ts
import { launch } from "https://deno.land/x/carol@v0.0.6/mod.ts";
import { dirname, join } from "https://deno.land/std@0.54.0/path/mod.ts";

const app = await launch({
  title: "Hello Deno!",
  width: 480,
  height: 320,
});

// Chromeが終了したらDenoのプロセスも終了させる
app.onExit().then(() => Deno.exit(0));

// Chrome側に関数を公開する
await app.exposeFunction("greet", (name: string) => `Hello, ${name}!`);

const folder = join(dirname(new URL(import.meta.url).pathname), "public");
app.serveFolder(folder); // "./public"ディレクトリ配下からファイルを配信する
await app.load("index.html"); // "./public/index.html"を読み込む
```

次に、上記の`mod.ts`と同一ディレクトリにpublicディレクトリを作成し、その中にChromeから読み込むHTMLとJSファイルを用意します。

```public/index.html
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <h2>Hello, Deno!</h2>
    <script src="/index.js" type="module">
    </script>
  </body>
</html>
```

```public/index.js
(async () => {
  window.alert(await window.greet("Deno"));
})();
```

この時点で以下のようなディレクトリ構造になっている想定です。

```
/
├── mod.ts
└── public
    ├── index.html
    └── index.js
```

それでは、`mod.ts`を実行してみます。

```shell
$ deno run --allow-env --allow-net --allow-read --allow-write --allow-run ./mod.ts
```

以下のように表示されれば成功です。

![Screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240412/d31c2356-df3e-a705-c1b6-fc7f3ac75961.png)


## 解説

先程の`mod.ts`の内容について解説します。

### `launch()`

`launch`関数を実行すると、Chromeが起動します。
`width`及び`height`プロパティでWindowの横幅と縦幅を指定できます。

```typescript
import { launch } from "https://deno.land/x/carol@v0.0.6/mod.ts";

const app = await launch({
  title: "Hello Deno!",
  width: 480,
  height: 320,
});
```

また、`args`プロパティを指定することで、Chromeを起動する際のコマンドライン引数をカスタマイズできます。

##### 例) Chromeをヘッドレスモードで起動する

```typescript
const app = await launch({ args: ["--headless"] });
```

### `app.onExit()`

```typescript
// Chromeが終了したらDenoのプロセスも終了させる
app.onExit().then(() => Deno.exit(0));
```

`app.onExit`は`Promise`を返却します。
この`Promise`はChromeが終了した際に`resolve`されます。
上記コードでは、Chromeの終了に合わせてDenoのプロセスも終了させています。

### `app.exposeFunction(name, fn)`

```typescript
// Chrome側に関数を公開する
await app.exposeFunction("greet", (name: string) => `Hello, ${name}!`);
```

`app.exposeFunction()`を使うと、DenoからChromeへ関数を公開できます。
第1引数はChrome側から参照する際の関数名、第2引数で公開する関数の処理を記述します。

公開した関数は`window`オブジェクト経由で使用することができます。

例えば、上記コードの場合、`window`オブジェクトに`greet`関数が追加されます。

```javascript
// 以下はブラウザ上で実行されるJavaScriptコードの想定
(async () => {
  const result = await window.greet("Deno"); // Promiseが解決されると、関数の戻り値が取得できます。
  window.alert(result); // Hello, Deno!
})();
```

### `app.load()`

ChromeにHTMLをロードさせる際に使います。

例えば、以下の例では、`app.serveFolder()`と組み合わせて、`public/index.html`をChromeに読み込むように指示しています。

```typescript
const folder = join(dirname(new URL(import.meta.url).pathname), "public");
app.serveFolder(folder); // "./public"ディレクトリ配下からファイルを配信する
await app.load("index.html"); // "./public/index.html"を読み込む
```

また、以下のようにURLを直接指定することも可能です。

```javascript
await app.load("https://www.google.co.jp/"); // ChromeにGoogleのトップページを読み込ませる
```

## 終わりに

DenoとHTMLを使ってGUIアプリを作る方法について解説しました。

興味があれば、是非使ってみてください。

また、Denoには他にも[deno_webview](https://github.com/eliassjogreen/deno_webview)というGUIライブラリがあるので、そちらもおすすめです。

## 補足

* carolの名前の由来は[carlo](https://github.com/GoogleChromeLabs/carlo)と[lorca](https://github.com/zserge/lorca)のアナグラムです。
* carolのライセンスはMITライセンスです。
* [carlo](https://github.com/GoogleChromeLabs/carlo)と[lorca](https://github.com/zserge/lorca)のソースをベースにしている箇所があり、そちらについては個別にライセンス・コピーライトが適用されます。
  * carloのライセンスはApache v2です(2020/05/31現在)
  * lorcaのライセンスはMITです(2020/05/31現在)