TauriでSVGのグラフ描画・保存をしてくれるデスクトップアプリ作成

この記事はニフクラ等を提供している[富士通クラウドテクノロジーズ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fjct)の23日目の記事です。

前日は[ystkfujii](https://qiita.com/ystkfujii)さんの[Kubernetes Service Hatoba上でIstioを使用したMultiCluster Service Meshを構築してみた](https://qiita.com/ystkfujii/items/cb5d6525fa0429f2a1d1)話でした。kubernetesやIstioに手を出している同期もちらほら出てきたので、自分もkubernetesを学んで大きなクラスターを作って遊んでみたいです。

---

今回は、個人開発でデスクトップアプリを作成した話になります。個人開発の利点の1つに、自分の好きにやっていいというものがありますが、今回のアプリ作成でもその例に漏れず、自由奔放にいろいろな技術に雑多に触れつつ練習がてらの開発を行いました。これに見習って、本記事の内容も雑多に、触れた技術、アプリ紹介、感想などを自由奔放に書いていくのが自然だと思うのでそうします。よろしくお願いします。

# TL;DR

![pieChart (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340350/14cd2929-a631-8713-b2fa-501544421464.png)

- 上の画像のように一日のスケジュールを円グラフで作成できるアプリ[pakpie](https://github.com/Atnuhs/pakpie)を作成しました。
- 同期の一人が欲しがっていて、ちょうどいい練習題材になりそうだったというのが作成理由です。
- このグラフはSVGでできています。Chart.jsなどは使用せず生成しています。
- まだ使いづらいです。機能追加しやすいようにリファクタリングできたので、これから機能追加をします。

# 環境

```package.json
    "devDependencies": {
        "@tauri-apps/cli": "^1.2.0",
        "@types/jest": "^29.2.4",
        "@typescript-eslint/eslint-plugin": "^5.43.0",
        "@typescript-eslint/parser": "^5.43.0",
        "eslint": "^8.28.0",
        "eslint-config-prettier": "^8.5.0",
        "jest": "^29.3.1",
        "prettier": "^2.7.1",
        "ts-jest": "^29.0.3",
        "typescript": "^4.8.2",
        "vite": "^3.0.2"
    }
```

- フレームワーク: [tauri](https://tauri.app/)
- テスト: [jest](https://jestjs.io/ja/)
- フォーマッター: [prettier](https://prettier.io/)
- リンター: [eslint](https://eslint.org/)
- 言語: TypeScript
- OS: Ubuntu 22.04 on WSL2
- CI/CD: GitHub Actions
- IDE: VS Code

## tauri

tauriはRustで書かれたGUIアプリ作成のためのフレームワークです。はじめはElectronを使用するつもりでしたが、tauriを先輩から教えてもらい、新技術っぽかったので採用しました✨（自己満足）。とは言っても、JSは基礎レベル、Rustは未経験だったので、ロジック側含め全体をTypeScriptで書いており、Rustの良さは味わえていません。[Tauri API](https://tauri.app/v1/api/js/)というものがあり、UI側からRustで実装された機能を使えるらしく、これを使うことでRustを知らずとも手軽によさが味わえそうです。

## jest

jestはJavaScriptのテストフレームワークです。TypeScriptにも対応しています。JS/TSには、ほかにもいくつかテストフレームワークがありましたが、今回はjestが一番最初に出てきたのでjestを用いてTDDしました。基本的な使い方に関して覚えることは少なく、今回の規模では`describe`, `test`, `expect`という3つの関数の使い方さえ知っていればテスト入門者の自分でも大体テストが書けました。比較関数は豊富にあったため、もうちょっと把握しておきたいです。

## wsl2

メインPCにwindows10を使っていて、windowsにゲームデータ、wsl2にコードを置いているので、GUIアプリの開発がしづらかったのですが、つい最近windows10でもwsl2のguiが使えるようになり、GUI開発のしやすい環境が整ってきました。tauriのクロスコンパイル機能とも相性が良いです。UIはOSによって細かな差が出てきましたが、今回の開発では大きな問題はありませんでした。
ちょくちょく固まってタスクマネージャーから落としたりしていましたが、`swap=0`としてからは安定しています。

## VS Code

編集したファイルを保存するだけで、フォーマッターの自動整形が走り、tauriのGUIプレビューが自動更新される環境を作れます。快適に開発できました。jestによるテストも自動で走る設定にできるらしいのですが、時間がなくて設定できていません。後で調べます。

# 開発手法

- TDD
- DDD
- atomic design
- 関数型プログラミング
- WSLでのGUI開発

## TDD

参考文献

https://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA-Kent-Beck/dp/4274217884/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=GYCK7II184T3&keywords=%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA&qid=1671726976&sprefix=%E3%83%86%E3%82%B9%E3%83%88%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA%2Caps%2C247&sr=8-1

この本は大学時代に購入していたのです。当時はFortranで数値計算屋をしており、テスティングフレームワークも少なく、TDDに手を付けられませんでした。今回めでたくTDDできました。

TDDの感想は、一言で、「安心感をもって開発できた」と感じました。テストを先に書くことで「自分が今何を作ろうとしているのか」「使用する側としての体験」がわかり、どのような機能が欲しいかを自然に導けた感覚があります。また、書き終えた実装はテストを通っているという実績があるため、「ここまでは絶対安全だな」と思える心理的な砦を築きながら開発を行うことができました。

また、「テストを書いてから実装する」というTDDの特長を、「テスト」と「実装」で分断したフェーズだと認識していました。この認識では、TDDを使うことでむしろ一歩が大きくなってしまいます。

しかし、「テストを書く+実装を最短で書きテストをPassする」と「テストケースを増やし、すべてPassするよう実装を書いていく」で分断しているという認識に変わりました。この場合、1つ目のフェーズは小さな一歩であり、心理的砦でもあります。2つ目のフェーズでもテストを少し増やして、それを充たす実装を書く、という小さな一歩のループを回すことでテストケースと実装を同時に充実させることができます。大きな一歩を踏みがちな自分としては、この気づきによって、TDDは自分の味方である、という感覚を得られました。

今回の開発では、TypeScriptを使用しており、フォーマッター・リンターもがっつり効いていたため、書籍での例よりもテストを実行する回数は減りました。

改善点として、ときどき一歩がデカすぎて、サイクルを回すのが大変でした。一歩がデカいことを自覚できていたので、次からは改善できるでしょう。また、プレゼンテーション層はテストを書きませんでした。理由が2つあって、1つはUIのプレビューを見ながら実装していたため安心感があったこと、2つ目が、プレゼンテーション層でTDDする方法がパッと思いつかなかったことです。結果的に困ることはありませんでしたが、プレゼンテーション層での効果的なTDD手法などを見落としている可能性があるなと感じています。これから調べます。

## DDD

参考文献

https://www.amazon.co.jp/s?k=%E7%8F%BE%E5%A0%B4%E3%81%A7%E5%BD%B9%E7%AB%8B%E3%81%A4%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E8%A8%AD%E8%A8%88%E3%81%AE%E5%8E%9F%E5%89%87&adgrpid=60120324664&gclid=CjwKCAiAnZCdBhBmEiwA8nDQxfp5R7n6Ehz8Tm2y5nNUAG-CfJJNxNg-0Ybb_DrIXgyiHzVJT1zjpxoC8XcQAvD_BwE&hvadid=618553085031&hvdev=c&hvlocphy=1009343&hvnetw=g&hvqmt=e&hvrand=13547067063747490920&hvtargid=kwd-330189736777&hydadcr=27268_14598057&jp-ad-ap=0&tag=googhydr-22&ref=pd_sl_4to0t7hi5x_e

https://booth.pm/ja/items/1835632

DDDはとても難しいと感じました。そもそも今回のアプリは小規模なもの（UIからの入力をもとにUIで表示するだけ）であり、基本はプレゼンテーション層で処理できます。永続化層も必要ありません。書籍の構成を見よう見まねで落とし込んでも、徒に行数が増えていくばかりで書いていて楽しくない状態がちょっと長かったです。

苦しいながらもいろいろな視点からアプリの機能を見て、別々に見ていたものを同一視してみたり、1つとして見ていたものを分けてみたりしていく中で、「ちょうどよい落としどころ」を見つけられたかなと思っています。

今回のアプリの最大の関心ごとは「円グラフをSVGで描画する」という部分であり、当初は雑にSVGElementの生成丸ごとドメイン層に置いていました。しかし、DOMイベントハンドラーをプレゼンテーション層に置いたため、DOMという関心事がドメイン層とプレゼンテーション層に分離してしまい、上からの命令を下の処理に受け流すだけの薄いメソッドが多くできてしまいました。

もう少し詳細に関心事を考えると、「スケジュールの開始時刻と終了時刻を表す`XX:XX`という形式の文字列2つを入力として、円グラフの扇部分の情報を幾何で取得する」という機能を最大の関心事としてとらえ、ドメインを作成することにしました。依存関係は下のように可視化されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340350/2df5ae4e-2d4a-73f8-6946-4ceb9ec941fa.png)

可視化するために使用したツール

https://github.com/pahen/madge

ドメイン層とプレゼンテーション層を上手く分けられました。プレゼンテーション層は付け焼刃でatomic designを使用していますが、vanillaのTSでも書きやすかったです。ドメイン層はTDDと組み合わせて開発しました。

改善点としては、共通化できそうな処理（domainのlengthとpointそれぞれが持っているplusメソッドなど）がいくつか見つかっています。現状のスキルではこれらを共通化してよいかわからないため放置しています。また、DDDで上手くいっている他人のコードをあまり見ることができておらず、勘違いしている部分が多いと思うので、手ごろな規模でDDDが成功しているプロジェクトを探したいと思います。

## WSL2でのGUIアプリ開発

https://forest.watch.impress.co.jp/docs/news/1458044.html

つい最近から、windows10のwslでもノーマルな状態でguiを扱えるようになりました。自分はあまりwindows側の環境で開発をやりたくない人間なので、とてもありがたかったです。

ただし、あくまでLinuxのUIなのでWindows用にクロスコンパイルしたバイナリと比較して、見た目、挙動が若干違います。今回の開発ではポインター形状の変化をWSL2のGUIで検証できませんでした。また、日本語が文字化け（「口」みたいになってしまう）してしまったのでアプリのUIは英語に決定されました。（あとはカラーピッカーの形状が異なるなど）
このように小さな相違があるので、がっつりUIの細部までこだわりたい場面では使用するosに合わせた開発環境を用意してやる必要があるようです。しかしざっくり開発する今回の用途には十分でした。

## 関数型プログラミング

```DOMHelper.ts
export const setHTMLAttributes =
    <T extends HTMLElement>(elem: T) =>
        (innerText: string) =>
            (styles: StyelAttributes) =>
                (attributes: Attributes): T => {
                    elem.innerText = innerText;
                    Object.entries(styles).forEach(([key, value]) => {
                        const old = elem.getAttribute("style");
                        elem.setAttribute(
                            "style",
                            (old == undefined ? "" : `${old}; `) + `${key}: ${value}`
                        );
                    });
                    Object.entries(attributes).forEach(([key, value]) =>
                        elem.setAttribute(key, value)
                    );
                    return elem;
                };
```

こんな感じの高階関数を書きました。

```atom.ts
const b = setHTMLAttributes(document.createElement("button"))(label)({
        "font-size": toPx(fontSize),
        "background-color": "#779eeb",
        "border-radius": "8px",
    })({});
```

このような感じで使えます。vanillaのTSでも宣言的な記述ができるのだなぁと、関数型を少し理解した気がします。アロー関数で高階関数を書くのは楽しいですね。ただし天才以外お断りコードになりやすく、塩梅がわかりません。

# アプリ

バイナリはここから落とせます。ブラウザがダウンロードを阻止してきます。

tauri公式のCI/CD用のymlがあったのでそれを流用しています。

https://github.com/Atnuhs/pakpie/releases

使い勝手はよくないです。現在はスケジュールの再登録ができないので、間違えたスケジュールを登録した場合はアプリを再起動しやり直しです。
グラフを画像としてダウンロードする機能もこの記事を書いている途中に完成しました。
テストを書きながらリファクタリングをしている時期が半分以上を占めているので、機能追加はこれから作っていきます。

# まとめ

JavaScriptを使い始めてから、半年程度ですが、だんだんとおもしろいものが作れるようになってきました。やはり目で見てにぎやかな成果物は作るの楽しいです。今回のアプリは20日程度の開発で1000行程度のコード量ですが、自分の中では新しいことにいろいろ手を出せて、濃密でした。開発速度向上をめざして、今後もいろいろ作っていく気持ちです。
TDDとは結構仲良くなれました。
DDDと仲良くなるにはもう少し時間がかかるかもしれません。
関数型プログラミングとは仲良くなり方が間違っている可能性があります。
次回はsvelteあたりと仲良くなりたいと画策しています。


---

この記事は富士通クラウドテクノロジーズAdvent Calendar 2022の23日目の記事でした。

明日は [kou-burger](https://qiita.com/kou-burger)さんのFJCTでの品質向上活動（テックブログ連携）です。
自分もテックブログが書けるレベルで自社製品に関わる技術について学んでいきたいと思います。以上です。