Fresh (とDeno) あれこれ
0. モチベ
fresh でのあれこれを整理するところ
アイコン
Tabler Icons は↓からコピって使う。なんか v.0.2 になってパワーアップしてる
Tabler Icons は線が太くそのままだと若干浮き気味なので、色をいじった方が良い。外部ファイルデータの取り回し
./static/data.json
の中身をコンポーネントで使いたいときの話。
現状では、以下のように扱っている。
-
dev.ts / main.ts
でファイルを読み込み、sessionStorage
に入れる -
./routes/_middleware.ts
のhandler
でストレージから取り出し、ctx.state
に入れる - データを利用したい部分の最上位の islands コンポーネントで context proveider を設定する
- データを利用したいコンポーネントで
useContext
する
ちょっと重い感じがするので改良したい。
今はもう普通に route 内の個別のページの handler で読み込み render に渡すことにした。
↓のような感じ
import { Handlers, PageProps } from "$fresh/server.ts"
export const handler: Handlers<StateData> = {
async GET(req, ctx) {
const name = req.url.split("/").at(-1)!
const node_data = await Deno.readTextFile(`./static/denoDoc_${name}.json`)
.then(tx => JSON.parse(tx))
.then(list => list[0]) as StateData["node_data"]
return ctx.render({ name, node_data });
},
}
export default function MyPage(props: PageProps<StateData>) { ... }
ドキュメントにあるように、/static
内のファイルは /ファイル名
でアクセスできる。しかし、await fetch("/ファイル名").then(res => res.json())
とすると、なぜか invalid URL のエラーになって動かない。
外側をクリックすると閉じるモーダル系コンポーネント
これのベストプラクティスが知りたい。
popover API を使ってみた。お手軽。
ただ、backdrop の下の要素を触れてしまうのがちょっと嫌。
関連属性に ComponentProps が未対応なので、vscode のエラー消しに↓みたいなのが必要
const ButtonWithPop = (props:ComponentProps<"button">&{
popovertarget:string,
popovertargetaction?: "show"|"hide"
}) => <button {...props}/>
背後を暗くしたい場合は、↓のように追加の head の中で::backdrop
のスタイルを設定する
import { Head } from "$fresh/runtime.ts"
export default function MyPage(props: PageProps<StateData>) {
return (
<>
<Head>
<style>{`
::backdrop { background-color: black; opacity: 0.6;}
`}</style>
</Head>
<Page {...props.data}/>
</>
)
}
.tsx 内部で Element を追加する
現状では
import { render, createElement } from "preact"
const elem = createElement("div", {
id: "temp",
style: {position: "fixed", width: "100%", height: "100%", top: "0", left: "0", zIndex: "10"}
})
const countianer = document.createElement("div")
render(elem, countianer)
document.getElementById(some_id)!.appendChild(countianer.firstElementChild!)
直接document.createElement
→ append でも良いのだけど、その場合は style 設定が面倒くさい...
画像ファイルを dataURL で取り回す
import { encode } from "https://deno.land/std/encoding/base64.ts"
const image_URL = await Deno.readFile(IMAGE_PATH)
.then( data => encode(data) )
.then( tx => `data:image/${IMAGE_PATH.split(".").at(-1)};base64,${tx}` )
const image = new Image()
image.src = image_URL
ファイル選択からの画像ファイルの読み込み
const fileInputElem = document.createElement("input")
const imgElem = document.createElement("img")
const img_file = fileInputElem.files[0]
const reader = new FileReader()
reader.onload = (e) => {
if (e.target !== null){
const { result } = e.target
if (typeof result == "string"){ imgElem.src = result }
}
}
reader.readAsDataURL(img_file)
生成したデータをダウンロード
const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'text/plain'})
const link = document.createElement("a")
link.download = "data.json"
link.href = URL.createObjectURL(blob)
link.click()
ページ遷移
onClick={(_ev) => window.open(path, "_self")}
相対パスの解決
fresh 関係ないけど
Deno.readTextFile("./xxx.ts")
のような Deno系関数における相対パスは、Deno.cwd()
を基準にして解決される(と思う)。
対して、dynamic import import("./xxx.ts")
における相対パスは、そのスクリプトが記述されたファイルのパスを基準にして解決される。
なので、Deno.cwd()
を基準にして dynamic import をしたい場合は、以下のように「絶対パス化+FileURL化」処理を経由する必要がある
import { toFileUrl, resolve } from "https://deno.land/std@0.200.0/path/mod.ts"
async function someFunc(){
await import(toFileUrl(resolve("./xxx.ts")).href)
}
以前にもこれに悩んで同じ解決法にたどり着いた痕跡がある。有名な話なのかも?
Deno から別プロセスを spawn しその出力を継続的に console に出す
const command = new Deno.Command(Deno.execPath(),{
args: ["run", "-A", "--watch", "main.ts"],
cwd: "./viewer", //実行ディレクトリの変更が必要な場合はここで設定
stdout: "piped",
stderr: "piped"
})
const process = command.spawn()
// preventClose とかのオプション設定は正直よくわからない
process.stdout.pipeTo(Deno.stdout.writable, {preventClose: true})
process.stderr.pipeTo(Deno.stderr.writable, {preventClose: true})
const status = await process.status
if (!status.success){
throw new Error(`Error in Viewer. Stop processing.`)
}
console.log(`Terminate Viewer.\n`)
「外部処理1 spawn → 終了 → 内部処理 → 外部処理2 spawn → 終了」のように、同一処理の中で複数の異なる Deno.Command を連続的に spawn する場合は preventClose: true
がないと処理1が終了した時点で console に pipeできなくなる。
ただ、preventClose: true
でも console の readble strem が開けないとかなんとかのエラーが割と出る。間隔が短いとだめなのだろうか?
クリップボードを読み書きする
await navigator.clipboard.readText()
.then( async (clipText) => {
if (clipText.length > 0){
const tx = funtion(clipText)
await navigator.clipboard.writeText(tx)
ok = true
} else {
window.alert("クリップボードが空です。")
}
})
.catch( e => {
window.alert(e)
})