webview_deno あれこれ
現在のあれ
- async function を
webview.bind
すると pending のまま解決されない
色々調べてたら「main thread 云々で worker を使うべし」みたいなのが出てきたので仕様だと思っていたが、開発側としてはバグ認識らしい
- worker を使っているとき、worker 側で
postMessage(text)
しても webview 側の.onmessage
が反応してくれない- 仕様なのかこっちのミスなのか、よくわからない。
素直?に worker 内で serve() して webview 側から fetch したほうが良さそう
- 仕様なのかこっちのミスなのか、よくわからない。
build 時に不足するとしてエラーになったもの (Win11)
-
vswhere.exe
:2017 以降の Visual Studio には標準で付属している -
VC tools x86/x64
:「MSVC v143 - VS 2022 C++ x64/x86 build tools (Latest)」らしい。Visual Studio 経由で読み込む -
Windows SDK
:これがない場合、以下のようなエラーが出るfatal error C1083: Cannot open include file: 'crtdbg.h': No such file or directory
記法あれこれ
雛形まとめのもとになったやつ
preact (renderToString) 型:hashedrock さんがツイートしていたもの + α
/** @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 = `
const button = document.querySelector("button")
button.addEventListener("click", async function() {
const result = await press("pressed", 123, new Date())
button.innerText = JSON.stringify(result)
})
`
function View(){
return (
<html>
<head></head>
<body>
<button> Press me ! </button>
<script dangerouslySetInnerHTML={{__html: script}}></script>
</body>
</html>
)
}
const webview = new Webview(true, {
width: 600,
height: 400,
hint: SizeHint.FIXED
})
webview.navigate(`data: text/html; charset=utf-8, ${encodeURIComponent( renderToString(View()) )}`)
webview.title = "deno app"
let counter = 0
webview.bind('press', (a,b,c) => {
console.log(a,b,c)
return { times: counter++ }
})
webview.run()
SolidJS (template) 型:mattn さんがツイートしていたもの + α
import { Webview, SizeHint } from "https://deno.land/x/webview/mod.ts";
const title = "PIYO"
const html = `
</html>
<head>
<meta charset="utf-8"/>
<script type="module" src="https://cdn.skypack.dev/twind/shim"></script>
<script type="module">
import {
createSignal,
onCleanup, } from "https://cdn.skypack.dev/solid-js"
import { render } from "https://cdn.skypack.dev/solid-js/web"
import html from "https://cdn.skypack.dev/solid-js/html"
const App = () => {
const [count, setCount] = createSignal(0)
const timer = setInterval(
() => setCount(count() + 1), 1000
)
onCleanup( () => clearInterval(timer) )
return html\`
<div>${title}</div>
<div>\${count}</div>
\`
}
render(App, document.body)
</script>
</head>
<body></body>
</html>`;
const webview = new Webview(true, {
width: 600,
height: 400,
hint: SizeHint.FIXED
})
webview.navigate(`data: text/html; charset=utf-8, ${encodeURIComponent(html)}`)
webview.title = "deno app"
webview.run()
Deno + SolidJS の話
SolidJS 側では、まず Deno との関わりについて↓で話されている
その後↓に移動しているが、こっちでは Ryan 氏は参加していないDeno で SolidJS は使える?
そもそも『使える』の定義がよくわかんない
- 『SolidJS で HTML を構築するスクリプトを記述する』なら、できた
- 『SolidJS ssr + SolidJS hydrate + Deno serve』も、できそうな気はする
Deno で SolidJS の JSX をエラーを出さずに書く
とりあえずの結論:無理
理由:型定義に JSX を使わない(使う気がない)関数を SolidJS がh
として提供しているため
長い
-
/** @jsx h */
を利用している場合、これは solidJS に対して以下のエラーを出す
-
React を使っている場合は、以下のようなエラーを出す
書いてある通り JSX の型のミスマッチ、つまり「JSX を吐き出す関数の返り値の型」の不一致がエラーを起こしている。なので「JSX を吐き出す関数」を SolidJS のものに差し替えればOKなのだが、うまくいかない。
- Deno 公式には JSX の取り扱いに関する doc があり、pragma
/** @jsxImportSource ~~~~~ */
の利用についての言及がある。 - SolidJS 公式にも この pragma の利用についての言及があり、jsx-runtime は
solid-js/jsx-runtime
orsolid-jsh/jsx-runtime
でアクセスできる。
が、pragma /** @jsxImportSource https://cdn.skypack.dev/solid-js */
を用いると、pragma 無しで jsx を記述したときと同じエラーが出る。
最悪なのが、何が悪いのかわからないこと。
jsx-runtime が悪いのか (SolidJS の問題)、runtime が適用できていないのか (Deno の問題)、エディタ(VSCode 拡張)の問題なのか、そもそもやり方が正しくないのか、判断がつかない。
- SolidJS 側は
JSX.IntrinsicElements
をちゃんとここで定義している - SolidJS が提供する jsx-runtime はこんな感じになっていて
import h from "https://esm.sh/v94/solid-js@1.5.4/h/types/index.d.ts"; export type { JSX } from "./jsx.d.ts"; import type { JSX } from "./jsx.d.ts"; declare function Fragment(props: { children: JSX.Element; }): JSX.Element; export { h as jsx, h as jsxs, h as jsxDEV, Fragment };
- この
https://esm.sh/v94/solid-js@1.5.4/h/types/index.d.ts
は↓でdeclare const _default: import("https://esm.sh/v94/hyper-dom-expressions@0.34.10/types/index.d.ts").HyperScript; export default _default;
- この
HyperScript
は↓になっているexport declare type HyperScript = { (...args: any[]): () => ExpandableNode | ExpandableNode[]; };
つまり:SolidJS が jsx-runtime の型定義に自分が定義した JSX を使っていないことが原因?
次の問題:JSX に依存しない jsx-runtime のもとで、なぜ Deno は JSX.IntrinsicElements の存在を要求するのか?
worker をサーバとして webview 側にデータを提供する
公式の ssr example をベースにすると楽
基本的には、以下のような作業を行う
- main.tsx に worker 作成の処理を追加する:
const worker = new Worker(...)
- main.tsx に worker 終了の処理を追加する:
worker.terminate()
- worker.ts (or .tsx) としてサーバの処理のスクリプトを作成するworker.ts
import { serve } from "https://deno.land/std@0.153.0/http/server.ts"; // CORS 制限に引っ掛かるのを回避するため const HEADER = new Headers({ 'Access-Control-Allow-Method': 'OPTIONS, GET', 'Access-Control-Allow-Origin': 'null', }) const server = serve((req) => { // リクエストを受けたときの処理 return new Response( "result", {headers: HEADER, }) }, { port: 8000 }); await server;
- webview 側から fetch したり link を貼ったりする
ローカルにある画像ファイルを worker 経由で <img> に貼る
それなりに重たい。画像が大きい+大量の場合、ヒープが足りないとかでエラーになる。 最適化とかよくわかんない。
コード
import { serve } from "https://deno.land/std@0.155.0/http/server.ts";
import { walk } from "https://deno.land/std@0.155.0/fs/mod.ts"
const HEADER_OPTION = {
'Access-Control-Allow-Method': 'OPTIONS, GET',
'Access-Control-Allow-Origin': 'null'
}
const images = walk("画像フォルダのパス", { maxDepth: 2, match: [/\.png$/, /\.jpg$/] })
const dict: Record<string, string> = {}
for await (const fl of images){
dict[fl.name] = fl.path.replaceAll("\\", "/")
}
const server = serve( async (req) => {
const IMAGES: Record<string, Uint8Array> = {}
await Object.entries(dict).reduce( (pre, [key, path]) => {
return pre.then( async () => {
const img = await Deno.readFile(path)
IMAGES[key] = img
} )
}, Promise.resolve() )
const url = new URL(req.url)
const pattern = new URLPattern({ pathname: '/images/:title' })
let HEADER = new Headers(HEADER_OPTION)
if (pattern.test(url)){
const { groups } = pattern.exec(url)!.pathname
if ("title" in groups ){
const called = decodeURI(groups.title)
if (called in IMAGES){
const type = called.split(".").at(-1)
const img = IMAGES[called]
HEADER = new Headers({...HEADER_OPTION, "Content-Type":`image/${type}`})
return new Response(img, {headers: HEADER, status: 200})
}
}
}
return new Response("", {headers: HEADER, status: 500})
}, { port: 8000 });
await server
//...
const App = () => {
// ...
return html`
<${For} each=${() => TITLES}>
${(name) => html`
<div>
<img loading='lazy' src='${() => `http://localhost:8000/images/${name}`}'>
<p>${name.split(".")[0]}</p>
</div>
`}
</For>
`
render(App, document.body)
-
webview からのアクセスは
origin = null
なので、設定によっては CORS 制限に引っかかる
(pax.deno.dev から弾かれてしまう) -
github の
raw.githubusercontent.com/...
から Javascript を読み込んだ場合、MIME type がtext/plain
なのでエラーになる
→ jsdelivr CDN を経由するように url を変更する。 具体的にはraw.githubusercontent.com/
をcdn.jsdelivr.net/gh/
に置き換え、ブランチ名を取り除く
<script type="module" ></script>
の間で import-export する
HTML 内のシンプルに実現するのは無理っぽい
↑にいくつか回答があるが、結局のところ「見た目の連携 + 実際に動かすための HTML の書き換え」になるわけで、追加の処理が不可避なら、内容がわかりやすい data URL でいいかなって思う