Deno + Webview + Preact / SolidJS でデスクトップアプリ
概要
deno_webview を使ってデスクトップアプリ(のような何か)を作る際の雛形のまとめ
以下のスクリプトが全ての基本になっていて、
-
const script = " ... "
の中身を記述するために使用するフレームワークの違い -
const script = " ... "
の中身を複数のファイルに分けて記述するかどうか
の2つの要素によって必要な処理が変わってくるという仕組み。
/** @jsx h */
import { Webview, SizeHint } from "https://deno.land/x/webview@0.7.4/mod.ts";
import { h, type VNode } from "https://esm.sh/preact@10.10.6"
import { renderToString } from "https://esm.sh/preact-render-to-string@5.2.2"
const script = " ... "
function View(){
return (
<html>
<head>
<script dangerouslySetInnerHTML={{__html: script}}></script>
</head>
<body>
</body>
</html>
)
}
const webview = new Webview(true)
webview.navigate(`data: text/html; charset=utf-8, ${encodeURIComponent( renderToString(View()) )}`)
webview.title = "deno app"
webview.run()
それぞれの形式の性質:ざっくり
preact
- ✅ JSX + typescript で記述できる
- ✅ import が通常通り利用できる = 型の情報を自動的に読み込んでくれる
- ❌ hydration のために
Client.jsx
とそのバンドル処理が必要 - ❌❌ fresh で良くない? と思ってしまう
solidJS
- ✅✅✅ deno で solidJS が利用できる
- 🆗 JSX で記述できるが、処理不可能な型エラーのための
@ts-ignore
が多め - ❌ import が通常通り利用できない = 型の情報を自分で書く必要がある
- ❌ .jsx を .js に変換する処理が必要
solidJS の tagged template literals
- ✅✅✅ deno で solidJS が利用できる
- 🆗 JSX では記述できないが、基本的には全ての型エラーに
@ts-ignore
なしで対処できる - ❌ import が通常通り利用できない = 型の情報を自分で書く必要がある
- ❌ HTML 部分に補完・チェックが効かない。あと、記法の癖がわりと強い。
サンプルとして作った時計 (JSX版 solidJS)
🔝
Starter:Minimum最もシンプルな形式のファイル構成。基本的には、以下の2つのみで動く。
-
main.tsx
:webview を起動するスクリプト。deno run -A --unstable main.tsx
する。 -
App.tsx / .jsx / .js
:HTML の中身を構築する preact / solidJS のスクリプト。
deno task start
すると、以下のようなウィンドウが起動する。
🔝
preact 版main.tsx
, App.tsx
, Client.tsx
の3点セット + deno task
用のdeno.json
+ Icon の tsx[1]。
main.tsx
-
App.tsx
はそのまま読み込み、<body><App /></body>
として挿入する (42行) -
Client.tsx
は deno_emit.bundle() [2]し、dangerouslySetInnerHTML
する (26,39行)
コード
App.tsx:いつもの preact
コード
Client.tsx:よくある Client.tsx。スタイルの都合上、<App>ではなく<body> が起点
コード
🔝
solidJS (JSX) 版main.tsx
, App.jsx
の2点セット + Compile.ts
+ deno task
用のdeno.json
。
main.tsx
-
App.jsx
は読み込み+ js への変換を行い、dangerouslySetInnerHTML
する (6,12,28,44行)
コード
App.jsx
- エディタ上での jsx の処理には preact の
h
を使う[1] -
@deno-types=
で solidJS の 型情報を読み込むと色々と楽になる (11,13行) - solidJS 用のコンポーネントが
h.JSX.Element
を返さないエラーは@ts-ignore
で無視(31行) - solidJS の関数に
h.JSX.Element
が渡されるエラーは@ts-ignore
で無視(末尾)
コード
Compile.ts
- 基本的には
Deno.readTextFile()
→Babel.transform(text, {presets: ["solid"]})
するだけ - ブラウザで動くよう、Babel が加える import 文を URL からの import に変える(24行)
コード
-
React を使っても solid との噛み合わせの悪さは解消されず、むしろエラーが増えた。 ↩︎
🔝
solidJS (tagged template literals) 版main.tsx
, App.js
の2点セット + deno task
用のdeno.json
。
main.tsx
-
App.js
は読み込んでそのまま、dangerouslySetInnerHTML
する (25,38行)
コード
App.js
- HTML 部分以外の記述は JSX 版と全く同じものが利用できる[1]
- HTML 部分の記法については公式に簡単な説明がある
-
@deno-types=
で solidJS の 型情報を読み込むと色々と楽になる (7,9,11行)
コード
-
ただし、any による型エラーを消そうとすると、色々と追加で定義することになる。 ↩︎
🔝
Starter: Use Components自作コンポーネントをローカルの別ファイルから読み込んで使う形式。
-
main.tsx
:webview を起動するスクリプト。deno run -A --unstable main.tsx
する。 -
App.tsx / .jsx / .js
:HTML の中身を構築する preact / solidJS のスクリプト。 -
Components.tsx / .jsx / .js
: コンポーネントを定義し、export するスクリプト。
deno task start
すると、以下のようなウィンドウが起動する。
🔝
preact 版main.tsx
, App.tsx
, Client.tsx
の3点セット + Components.tsx
+ deno task
用のdeno.json
+ Icon の tsx。
main.tsx:Minimum の場合と同じ
App.tsx:普通に import して使う (13,40-41行)
コード
Client.tsx:Minimum の場合と同じ
Components.tsx: いつもの preact
コード
🔝
solidJS (JSX) 版main.tsx
, App.jsx
の2点セット + Components.jsx
+ Compile.ts
+ deno task
用のdeno.json
。
main.tsx
-
App.jsx
の扱いは Minimum と同じ -
Components.jsx
は読み込み+ js へ変換+ base64 encode で data URL にする(39-45行) - さらに
webview.bind()
を使ってこの URL をApp.jsx
内で呼び出す関数を用意する (70行)
コード
App.jsx
- bind された関数から得た data URL を
import()
で読み込み、普通に使う (18-23,50行) - bind された関数は
App.jsx
内では未定義なので、エラーを@ts-ignore
する (21行) - その他は Minimum と同じ。コンポーネントの型を書いておけばエラーは出ない[1]
コード
Compile.ts:Minimum の場合と同じ
Components.jsx:普通の solidJS。半分くらい JSDoc の記述が占める
コード
-
もちろん、返り値の型として
h.JSX.Element
という嘘を記述しておかなければエラーになる ↩︎
🔝
solidJS (tagged template literals) 版main.tsx
, App.js
の2点セット + Components.js
+ deno task
用のdeno.json
。
main.tsx
-
App.js
の扱いは Minimum と同じ -
Components.jsx
は読み込み+ base64 encode して data URL を作る(30-31行) - さらに
webview.bind()
を使ってこの URL をApp.jsx
内で呼び出す関数を用意する (56行)
コード
App.js
- bind された関数を使って data URL を得て、
import()
を使って読み込む (24-29行) - bind された関数は
App.jsx内
では未定義なので、エラーを@ts-ignore
する (27行) - コンポーネントへ props を渡す際には、無名関数でラップするのがおすすめ[1] (55行)
コード
Components.js: 普通の tagged template literals
コード