class: center, middle # ブログ内引用グラフを自作して引用一覧を作った話 --- # こんにちは〜
- [Windymelt](https://www.3qe.us/)です - [株式会社はてな](https://hatena.co.jp/)から来ました - 普段ははてなブックマークとかの開発してます --- # はてなブログ - https://hatenablog.com - エンジニアに人気なブログツール --- # エンジニアあるある - シリーズものとかを書くんだけど前記事にさかのぼってリンクを足すの面倒 - 以前の記事を修正した記事を作ったけど以前の記事にリンク足すの面倒 --- # トラバがない - 昔は**トラバ**で通じてたけど現代人はトラバ知らなそう - トラバ: トラックバック。その記事を引用すると引用先に通知とかが行き、引用先にも表示される仕組み --- class: center, middle # 作るか〜 --- # ガッとやればいけるのでは - なんとかして記事同士の引用関係を抽出する - なんとかしてDBに保存する - なんとかしてフロントエンドからそれを参照する --- # できた - https://blog.3qe.us/ の記事末尾に出るようにした - 例: [Facebookが開発した圧縮アルゴリズムZstandardについて調べた(非常に高速)(今日から使えます) - Lambdaカクテル](https://blog.3qe.us/entry/2022/10/10/120114) - リポジトリ - [windymelt/blogtrack: はてなブログの自己引用可視化ツール](https://github.com/windymelt/blogtrack) - 自作ツールなので社とはぜんぜん関係ない非公式のやつです(一応) ---
--- # おもしろポイント - 引用関係の可視化はグラフDBの[Neo4j](https://neo4j.com/)を利用 - Neo4j AuraというSaaSがあるのでアカウント作って放り込めばいい - これぐらいなら無料枠で余裕 - 「この記事引用してる記事一覧出して〜」できる - バックエンドはScalaで書いてCloudrunでホスト - フロントエンドはScalaで書いてCloudflareでホスト --- # おもしろポイント - 引用関係の可視化はグラフDBの[Neo4j](https://neo4j.com/)を利用 - Neo4j AuraというSaaSがあるのでアカウント作って放り込めばいい - これぐらいなら無料枠で余裕 - 「この記事引用してる記事一覧出して〜」できる - バックエンドはScalaで書いてCloudrunでホスト - **フロントエンドはScalaで書いて**Cloudflareでホスト --- class: center, middle # ?????????? --- # Scala.js - 影薄いけど10年くらいちゃんと開発されてるAltJSの一派 - [Scala](https://scala-lang.org/)をそのままJSにトランスパイルする - [紹介ブログ記事もあるよ](https://blog.3qe.us/entry/2023/10/02/221036) ```scala import org.scalajs.dom @main def Main(): Unit = println("Hello, JS!") dom.document.querySelector("#app").innerHTML = "Hello!" ``` --- # モアベターTypeScript的世界観 - DOM操作ライブラリがある - `org.scala.js.dom`をインポートする - UIライブラリもある - [Laminar](https://laminar.dev/) - TypeScriptのライブラリの型定義読み込める - [ScalablyTyped](https://scalablytyped.org/docs/readme.html) - ScalaでTSライブラリの補完が効く異常体験ができるぞ!!! - 型が頑丈 - コレクションメソッドつよい - Scala標準ライブラリが使えてお得 --- # Laminar - https://laminar.dev/ - JSX的世界観でつらつら書く - 見覚えある感じ - リアクティブにデータ操作するのが面白い --- ```scala val citationVar: Var[Option[Seq[Citation]]] = Var(None) def appElement: Element = div( cls := "ui card container", div( cls := "content", div( * cls <-- citationVar.signal.map { case Some(_) => "ui inverted dimmer" case None => "ui active inverted dimmer" }, div( cls := "ui text loader", "Loading", ), ), div( div( cls := "header", "This article is cited by:", ), cls := "ui relaxed divided list", * children <-- citationVar.signal.map { opt => opt match { case Some(cit) => cit.map(citationElement) case None => Seq() } }, * Signal.fromFuture(getCitations()) --> { citations => citations match { case Some(citations) => citationVar.set(Some(citations)) case None => // TODO: error handling } }, ), ), ) ``` --- # バックエンドとの通信どうすんの - [Smithy](https://smithy.io/2.0/index.html)ってやつ使う - Amazonが出してるIDL (Interface Description Language) - gRPC的なやつ - [Smithy4s](https://disneystreaming.github.io/smithy4s/)ってやつがフロントエンド/バックエンドの通信処理や必要な型をコンパイル時生成する - 自分からはメソッド呼ぶだけでいい - フロントもバックもScalaなので共通化できる - JVMプロジェクトとJSプロジェクトが同じScalaソースをコンパイルして使うという夢 --- ## Smithyファイル(一部) ```smithy $version: "2" namespace io.github.windymelt.blogtrack.api @documentation("新規エントリが利用可能であることをサーバに通知する。") @http(method: "POST", uri: "/notify", code: 200) operation NotifyNewEntry { input: NotifyNewEntryInput output: NotifyNewEntryOutput errors: [BTError] } @input structure NotifyNewEntryInput for BlogTrackService { @required entryUrl: Url } ``` --- ## 生成されたコードを使う様子 ```scala // 呼ぶと今開いてるページを引用している記事の一覧が返る def getCitations(): Future[Seq[Citation]] = { val currentUrl = dom.window.location.href currentUrl match { case myBlogRegex() => * val io = BlogTrackClient.blogTrackClient.use { c => * c.readCite(api.Url(currentUrl)) * } io.unsafeToFuture() .map( _.citation.whatCitedMe.map(cit => Citation(cit.title, cit.url.toString, "", cit.tags) ) ) case _ => Future.failed(new Exception("Not my blog")) } } ``` --- # ビルドどうすんの - Viteプラグインがある