shadow-cljsで自宅LAN向けのWebアプリ作った

shadow-cljs使ってなんか作るRTAはっじまるよー

## 現在15日の金曜夜、計測スタート

### スケルトンプロジェクトを作成

shadow-cljsは一から使った事ないのでさっとググって読んでみる
これ良さそうだな

https://qiita.com/lambda-knight/items/a69df35405b26f7a79cf

最初のスケルトンプロジェクトはこれでいこう
なりお世話になったが、 shadow-cljs と reagent 入れた所でおわりか

#### reagentのアップグレード

ついでに [GitHubのreagent](https://github.com/reagent-project/reagent) を見ながら `1.2.0` の最新にしたらclojure-lspにより怒られが発生

![Screenshot from 2023-12-16 14-40-58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56308/b9de864b-8f73-dcb9-1505-f26d8dd78085.png)

そういや `reagent.dom` を使ってくださいみたいなのあった気がする。
Examplesを見るとこのような感じで紹介されている事を確認

```clojure
(ns example
  (:require [reagent.core :as r]
            [reagent.dom :as rd]))

(defn mountit []
  (rd/render [childcaller]
            (.-body js/document)))
```

`r/render`を`rd/render`に変更するだけだな。

よし通ったし画面も表示される。

#### Reactを17系にダウングレード

確かに画面は表示されるが、
デベロッパーツールを開くと「この書き方は18系としては古いので新しい書き方に変更してください」とか怒られる

https://zenn.dev/kohski/articles/create-react-app-error-v18

reagentのREADME.mdを確認すると、18系は動かしてなさそう

> or by adding Cljsjs React packages to your project:
> 
> ```clojure
> [cljsjs/react "17.0.2-0"]
> [cljsjs/react-dom "17.0.2-0"]
> ```
> 
> Note: Reagent is tested against React 17, but should be compatible with other versions.

17系までしかテストしてません！そんなことある？
Issueを軽く覗いたら「React 18に早く対応してくれ！」みたいなのが複数あったからそのうち対応されそう。

- tasks
    - [x] GitHubでリポジトリを作る
    - [x] shadow-cljsのプロジェクトを作る
    - [x] reagentを入れる
    - [ ] ルーターを入れる
    - [ ] re-frameを入れる
    - [ ] 自宅サーバーに向けてAjaxを飛ばしてJSONを受け取る
    - [ ] Webアプリをガリガリ書く

### ルーターを入れる

複数画面でページ遷移出来るものが作りたい
少し調べたら `react-router-dom` の機能を使って上手く書くサンプルが出てきた

https://github.com/thheller/react-router-cljs/blob/master/src/main/demo/app.cljs

```clojure
(defn root []
  [:> Router
   [:div
    [:nav
     [:ul
      [:li
       [:> Link {:to "/"} "Home"]]
      [:li
       [:> Link {:to "/about/"} "About"]]
      [:li
       [:> Link {:to "/users/"} "Users"]]]]
    [:> Route {:path "/" :exact true :component Index}]
    [:> Route {:path "/about/" :component About}]
    [:> Route {:path "/users/" :component Users}]
    ]])
```

シンプルだがダサい、出来れば避けたいな
Next.jsとか流行るわけだ

職場のプロジェクトでスマートに解決してた気がする……
漁ったら[accountant](https://github.com/venantius/accountant) + [bidi](https://github.com/juxt/bidi)を併用しているのを見つけた
記述量は増えるけどURLパラメータとかも使えるし頑張ってみるか

こうなった

src/core.cljs
![Screenshot from 2023-12-17 11-37-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56308/4c5d4188-116f-671c-b21a-c5c41983da19.png)

src/view/views.cljs
![Screenshot from 2023-12-17 11-34-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56308/44a86142-138d-c21b-3fb2-d845c0dd9e0b.png)

結局の所は力技で全部書く部分があって、
誰が貧乏くじを引くかの違いでしかないようだ

setup-routesの部分はaccountantやbidiの威力が出ててよき
このままいこう

- tasks
    - [x] ルーターを入れる
    - [ ] re-frameを入れる
    - [ ] 自宅サーバーに向けてAjaxを飛ばしてJSONを受け取る
    - [ ] Webアプリをガリガリ書く

### re-frame を入れる

前述で社内のプロジェクトを写経している間に re-frame をついでに入れてしまったが
全体的な流れとしてはほぼ同時に実装するしかなかった

https://qiita.com/lagenorhynque/items/3770e520bee0007e417c

全体的な流れとしてはカマイルカ氏の記事通りにすすめていけばOK
つか、社内のプロジェクトも中身はこの記事とほぼ一緒やんな……

- [event](https://qiita.com/lagenorhynque/items/3770e520bee0007e417c#event-handler)
    - dbファイルの`initialize-db`周りは特にお世話になった
    - db値を上書きするeventの存在
- [sub](https://qiita.com/lagenorhynque/items/3770e520bee0007e417c#event-handler)
    - subを定義することでdb値をview内で使う事ができる

急いでいるのでテストやロジックは後でやろう！（死亡フラグ

- tasks
    - [x] re-frameを入れる
    - [ ] 自宅サーバーに向けてAjaxを飛ばしてJSONを受け取る
    - [ ] Webアプリをガリガリ書く

### 自宅サーバーに向けてAjaxを飛ばしてJSONを受け取る

既にWebAPIサーバーだけは用意してある

```bash
$ curl http://localhost:13000/novel/favorites | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1112  100  1112    0     0   361k      0 --:--:-- --:--:-- --:--:--  361k
[
  {
    "type": "kakuyomu",
    "id": "16817330662422093388",
    "title": "【第2章開始！】転生チートの空間魔法使いは正体隠して目立ちたい！～国でもトップレベルの美人・美少女パーティに探されてますが、それ俺ですとは言えません～",
    "page": 32,
    "read": 0,
    "novelupdated_at": "2023-11-05T11:03:20.000Z",
    "created_at": "2023-11-05T17:37:10.000Z",
    "updated_at": "2023-11-06T16:48:41.000Z"
  },
  ...以下省略
]
```

後はAjaxを発射するだけ、JavaScriptにはFetchAPIがあるけど、
それ準拠でシンプルな挙動してくれるライブラリないかなぁ？

https://github.com/lambdaisland/fetch

これ良さそうやん
shadow-cljsにはjs-awaitってマクロが用意されてて、Promise値を待つという事は超簡単に実装出来るらしい。

https://apribase.net/2023/08/30/shadow-js-await/

---

あー、でもre-frameの作りが今すぐ`db`値を更新しろって感じだから、
イベント発行だけして、後はよしなにやってもらう作りと相性悪いんか
だから re-frame を使うならば `re-frame-fetch-fx`を使った方が良さそう

https://github.com/superstructor/re-frame-fetch-fx

---

ん〜………どっちにするかなぁ
`re-frame-fetch-fx`はたらい回しにされて、
イベント宣言がアホ程増えるからあんま好きじゃないんよなぁ

今回はshadow-cljsのjs-await使ってみたいしそっちにするか
event関連は全てdispatch-syncで実装すればいいんじゃない？

![Screenshot from 2023-12-17 22-16-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56308/ee458e7b-551a-00b0-0407-38327501ccb1.png)

clojure-lspがマクロを認識出来ずにresが宣言されてないよ！って怒ってるがまぁヨシ

こんな感じで準備しといて……

![Screenshot from 2023-12-17 22-18-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56308/5bba2c19-7616-ede5-f7b8-4c47ea7ec66d.png)

![Screenshot from 2023-12-17 22-18-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56308/614f3cc8-cf47-8bc0-07ea-3a8ea342bf57.png)

やったぜ

- tasks
    - [x] 自宅サーバーに向けてAjaxを飛ばしてJSONを受け取る
    - [ ] Webアプリをガリガリ書く

### 感想と懸念点

腰が重くなる理由の導入の箇所を全部何とか出来たので後は実装するだけ、気が楽になった
年内を目標に少しずつ作りこんで行こうかな

https://github.com/miyabisun/novel-browser

ファイルは一応公開しているけど、Ajaxの送信先が非公開のオレオレAPIだから何の参考にもならない気がする……

懸念点としてはこんな感じ

- CSSのスマートな実装方法を検討・導入
    - SASSを見張って即反映する (leiningenなしで実現出来るのだろうか？)
    - CSS in JSならぬ「CSS in CLJS」 (検索したら結構出てきた)
    - 面倒だから`:style`でよくね？
- ページ遷移
    - 頑張って accountant / bidi を導入したらから多分楽勝でしょう (楽観視)
    - ああ、URLパラメータの取得方法確認してない
- 同期処理・値変更時の反映
    - dispatch-syncで値を更新しているから、複数のPOSTのAjaxを飛ばして値を更新する所がちと不安
    - re-frameのsubscribeを使っているので問題ないと信じたい

## 参考資料達

https://qiita.com/lambda-knight/items/a69df35405b26f7a79cf

shadow-cljs ユーザーズガイド
https://t-cool.github.io/shadow-cljs-users-guide-ja/docs/UsersGuide.pdf

https://github.com/reagent-project/reagent

https://qiita.com/lagenorhynque/items/3770e520bee0007e417c

https://github.com/lambdaisland/fetch

https://apribase.net/2023/08/30/shadow-js-await/

https://github.com/superstructor/re-frame-fetch-fx

## 経緯

ミーティングで「もう12月っすね」「今年のClojureのアドベントカレンダースカスカですね」「みやびさんどうですか？」という話の流れになって

アドベントカレンダーの存在すら忘れ、4年間もサボっていた事に気付く私
「じゃあなんか書きますか」って事で動きはじめた

うーん、Rustで自鯖の強化しようと思ってたんだけどな
同じものを2言語で作ってもしょうがないし別の何かを考えますか

---

折角Clojureを全社的に使っている会社に勤めているので
ミーティングで相談してみる

自分「やっぱりNode.jsの人間なんで、ClojureScriptでなんか作りたいですね」
自分「まだ shadow-cljs + reagent + re-frame がメジャーなんですかね？」
同僚A「そうみたい、これを超える程の構成は見てないなぁ」
同僚B「ClojureScript や shadow-cljs はトランスパイル後のJSファイルが重いから、もっとネイティブなJSに近いものを生成してくれるものも色々と登場はしているけどね」
自分「へー、とりあえず shadow-cljs でなんか作ってアドベントカレンダーに備えます」

このネイティブに近いJSファイル吐き出してくれるものも
ちらっと気になったのでググってみたものの出てこないのでもういいや

この話題を出した時点でもう12/11、名前とちょっとした使い方くらいしか知らない shadow-cljs の勉強をしないとアドベントカレンダーにはもう間に合わない

---

でも動機だ、動機が無いと私は何も作れない
12/15の金曜日までは動機の捻出に力を注いできた

最近 BOOX Palma というE Inkのスマホサイズのタブレットを買った
夜寝る時に布団の中へ飛び込んでスマホを眺めるとブルーライトで眠れなくなっちゃうからね
そこで問題が発生したのを思い出した

いつもスマホで読んでいる小説ブラウザとの同期が取れない
これでは布団の中でなろう小説を読んで、いつ異世界転生しても良いようにとの予習が出来ないではないか

なので最近 BOOX Palma は布団の側で転がっているだけになっている
なんとかしようと早数ヶ月、なんとかする日がついにやって来たのだ

そうだ、小説ブラウザを再現すればいいじゃん
これはNext.jsでささっと作ろうと思っていたが
獲物のLiveScriptがすっかりオワコンで言語難民生活してた所だったんだ
これをshadow-cljsでやればいいじゃん。