Open15

webview_deno あれこれ

ピン留めされたアイテム
nikogolinikogoli

モチベ

deno_webview を実際に使ってみた系の情報が見当たらないので、見つけたものや試したことをここに記録する

雛形のまとめを書いた
https://zenn.dev/nikogoli/scraps/30e2a823950aba

nikogolinikogoli
  • worker を使っているとき、worker 側で postMessage(text)しても webview 側の .onmessage が反応してくれない
    • 仕様なのかこっちのミスなのか、よくわからない。
      素直?に worker 内で serve() して webview 側から fetch したほうが良さそう
nikogolinikogoli

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
    
nikogolinikogoli

記法あれこれ

雛形まとめのもとになったやつ

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

Deno + SolidJS の話

SolidJS 側では、まず Deno との関わりについて↓で話されている
https://github.com/solidjs/solid/issues/135
その後↓に移動しているが、こっちでは Ryan 氏は参加していない
https://github.com/solidjs/solid/discussions/873#discussioncomment-2297424

nikogolinikogoli

Deno で SolidJS は使える?

そもそも『使える』の定義がよくわかんない

  • 『SolidJS で HTML を構築するスクリプトを記述する』なら、できた
  • 『SolidJS ssr + SolidJS hydrate + Deno serve』も、できそうな気はする
nikogolinikogoli

Deno で SolidJS の JSX をエラーを出さずに書く

とりあえずの結論:無理
理由:型定義に JSX を使わない(使う気がない)関数を SolidJS がhとして提供しているため

長い
  • /** @jsx h */を利用している場合、これは solidJS に対して以下のエラーを出す

  • React を使っている場合は、以下のようなエラーを出す

書いてある通り JSX の型のミスマッチ、つまり「JSX を吐き出す関数の返り値の型」の不一致がエラーを起こしている。なので「JSX を吐き出す関数」を SolidJS のものに差し替えればOKなのだが、うまくいかない。

が、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 の存在を要求するのか?

nikogolinikogoli

worker をサーバとして webview 側にデータを提供する

公式の ssr example をベースにすると楽

基本的には、以下のような作業を行う

  1. main.tsx に worker 作成の処理を追加する: const worker = new Worker(...)
  2. main.tsx に worker 終了の処理を追加する: worker.terminate()
  3. 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;
    
  4. webview 側から fetch したり link を貼ったりする
nikogolinikogoli

ローカルにある画像ファイルを worker 経由で <img> に貼る

それなりに重たい。画像が大きい+大量の場合、ヒープが足りないとかでエラーになる。 最適化とかよくわかんない。

コード
worker.ts
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
script.ts
//...
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)
nikogolinikogoli
  • 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/ に置き換え、ブランチ名を取り除く

https://branditechture.com/fix-mime-type-error-github/