Closed12

Deno + Webview + Preact / SolidJS でデスクトップアプリ

nikogolinikogoli
nikogolinikogoli

以下のスクリプトが全ての基本になっていて、

  • 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()
nikogolinikogoli

それぞれの形式の性質:ざっくり

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 部分に補完・チェックが効かない。あと、記法の癖がわりと強い。
nikogolinikogoli

Starter:Minimum 🔝

最もシンプルな形式のファイル構成。基本的には、以下の2つのみで動く。

  • main.tsx:webview を起動するスクリプト。deno run -A --unstable main.tsx する。
  • App.tsx / .jsx / .js:HTML の中身を構築する preact / solidJS のスクリプト。

deno task startすると、以下のようなウィンドウが起動する。

nikogolinikogoli

preact 版 🔝

main.tsx, App.tsx, Client.tsxの3点セット + deno task用のdeno.json + Icon の tsx[1]

main.tsx

  • App.tsxはそのまま読み込み、<body><App /></body>として挿入する (42行)
  • Client.tsxdeno_emit.bundle() [2]し、dangerouslySetInnerHTMLする (26,39行)
コード

App.tsx:いつもの preact

コード

Client.tsx:よくある Client.tsx。スタイルの都合上、<App>ではなく<body> が起点

コード
脚注
  1. Icon の tsx ファイルをリモートから import すると renderToString がエラーを出すのでローカルに置いている。エラーになるようなものを使うなって話だが、solid 側と揃えたいので... ↩︎

  2. Deno 1.22 で削除されたDeno.emit() をモジュールとして分離したもの。ネットに転がっている情報はこの変更に未対応なものが多いので注意 ↩︎

nikogolinikogoli

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行)
コード
脚注
  1. React を使っても solid との噛み合わせの悪さは解消されず、むしろエラーが増えた。 ↩︎

nikogolinikogoli

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行)
コード
脚注
  1. ただし、any による型エラーを消そうとすると、色々と追加で定義することになる。 ↩︎

nikogolinikogoli

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すると、以下のようなウィンドウが起動する。

nikogolinikogoli

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

コード
nikogolinikogoli

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 の記述が占める

コード
脚注
  1. もちろん、返り値の型としてh.JSX.Elementという嘘を記述しておかなければエラーになる ↩︎

nikogolinikogoli

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

コード
脚注
  1. 詳しくは調査したスクラップを参照 ↩︎

このスクラップは2024/04/07にクローズされました