From 102f8a74d28c425e501180877bcf0b8ee020ae9f Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Thu, 9 Oct 2025 10:13:49 -0400 Subject: [PATCH] remove preact, GPT fixes --- app.tsx | 5 +-- deno.json | 3 -- deno.lock | 35 +++++++-------- hmr/hmr-listen.tsx | 10 ----- hmr/hmr-signal.tsx | 47 -------------------- index.html | 18 ++++---- server.ts | 108 +++++++++++++++++++++++++++++++++++++-------- 7 files changed, 117 insertions(+), 109 deletions(-) delete mode 100644 hmr/hmr-signal.tsx diff --git a/app.tsx b/app.tsx index 0ba6200..24cb891 100644 --- a/app.tsx +++ b/app.tsx @@ -4,12 +4,11 @@ import React from "react"; export function App(){ -console.log(React.useState(1)) - const [countGet, countSet] = React.useState(0); + const [countGet, countSet] = React.useState(2); return <> -

test paragraph

+

test paragraph

} diff --git a/deno.json b/deno.json index 934d9e5..995a6fc 100644 --- a/deno.json +++ b/deno.json @@ -13,9 +13,6 @@ "react":"https://esm.sh/react@19.2.0", "react/":"https://esm.sh/react@19.2.0/", "react-dom/":"https://esm.sh/react-dom@19.2.0/", - - "@preact/":"npm:@preact/", - "signals-original": "npm:@preact/signals", "react-original": "https://esm.sh/react@19.2.0" } } diff --git a/deno.lock b/deno.lock index 3a8a72f..e733874 100644 --- a/deno.lock +++ b/deno.lock @@ -1,26 +1,25 @@ { "version": "5", "specifiers": { - "npm:@preact/signals@*": "2.3.2_preact@10.27.2" + "jsr:@std/media-types@*": "1.1.0" }, - "npm": { - "@preact/signals-core@1.12.1": { - "integrity": "sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==" - }, - "@preact/signals@2.3.2_preact@10.27.2": { - "integrity": "sha512-Q22avIn4z0BQnmFeo6Y5HCnJTo8VufN84zN51OtqeNgZOVCYgdwEOcJKVX1x/IrjRVxUnOy6Ubn7H5aVFujXaQ==", - "dependencies": [ - "@preact/signals-core", - "preact" - ] - }, - "preact@10.27.2": { - "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==" + "jsr": { + "@std/media-types@1.1.0": { + "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" } }, - "workspace": { - "dependencies": [ - "npm:@preact/signals@*" - ] + "redirects": { + "https://esm.sh/scheduler@^0.27.0?target=denonext": "https://esm.sh/scheduler@0.27.0?target=denonext" + }, + "remote": { + "https://esm.sh/react-dom@19.2.0/client": "489f23e19c36110c4d6361c09cd969e304665799d974ddc90ea36d9a7346e249", + "https://esm.sh/react-dom@19.2.0/denonext/client.mjs": "d12f88ffe04230ba1e9722c97157d86b8e287e71911de06488187abbdd216726", + "https://esm.sh/react-dom@19.2.0/denonext/react-dom.mjs": "d7bae53f5407fc0f29e04fd1866deb4a17e4e0df5ede1cdc50c791fbe21c3b06", + "https://esm.sh/react@19.2.0": "c69a4f37d5f2e04b5088c1fe6bc24f6bf0728704544f902b553d0852a7fdfae0", + "https://esm.sh/react@19.2.0/denonext/jsx-runtime.mjs": "75cd15682ab2ac7ffa971367359974541bbcfc8af7217c3fdb62f2a21b9be765", + "https://esm.sh/react@19.2.0/denonext/react.mjs": "6ffb8433fe90ae15017245e8b461f5824590e5dee07568afe31bab19e10b03ae", + "https://esm.sh/react@19.2.0/jsx-runtime": "11cfe447d2aed82e4469f0b856899b053d5fe1daf059d53cbfa75c5e5b706acc", + "https://esm.sh/scheduler@0.27.0/denonext/scheduler.mjs": "b1ba1723bf253d62b0aa1c3c0603d0d025010642158b0e746ae1fd6f7500deeb", + "https://esm.sh/scheduler@0.27.0?target=denonext": "d5e7a8b3ddf1c77fdd56083413b9ed29648e017b1000b85b3d3059a226627742" } } diff --git a/hmr/hmr-listen.tsx b/hmr/hmr-listen.tsx index cd029b3..50caf18 100644 --- a/hmr/hmr-listen.tsx +++ b/hmr/hmr-listen.tsx @@ -1,5 +1,4 @@ import { HMR } from "./hmr-react.tsx"; -import { GroupSignal, GroupSignalHook } from "./hmr-signal.tsx"; const FileListeners = new Map() as Mapvoid>>; export const FileListen =(inPath:string, inHandler:()=>void)=> @@ -14,18 +13,9 @@ Socket.addEventListener('message', async(event:{data:string})=> { // When a file changes, dynamically re-import it to get the updated members // send the updated members to any listeners for that file - - GroupSignal.reset(); - const reImport = await import(document.location.origin+event.data+"?reload="+Math.random()); FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport)); - - GroupSignal.swap(); - - GroupSignalHook.reset(); HMR.update(); - GroupSignalHook.reset(); - }); Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")}) const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); \ No newline at end of file diff --git a/hmr/hmr-signal.tsx b/hmr/hmr-signal.tsx deleted file mode 100644 index b37f696..0000000 --- a/hmr/hmr-signal.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as SignalsParts from "signals-original"; -import DeepEqual from "https://esm.sh/deep-eql@4.1.3"; - -type Entry = [signal:SignalsParts.Signal, initArg:T]; - -function ProxyGroup(inFunc:(initArg:T)=>SignalsParts.Signal) -{ - let recordEntry:Entry[] = []; - let recordEntryNew:Entry[] = []; - let recordIndex = 0; - const reset =()=> recordIndex = 0; - const swap =()=> - { - recordEntry = recordEntryNew; - recordEntryNew = [] as Entry[]; - }; - const proxy =(arg:T)=> - { - const lookupOld = recordEntry[recordIndex]; - if(lookupOld && DeepEqual(lookupOld[1], arg)) - { - recordEntryNew[recordIndex] = lookupOld; - recordIndex++; - return lookupOld[0]; - } - else - { - const sig = inFunc(arg); - recordEntryNew[recordIndex] = [sig, arg]; - recordEntry[recordIndex] = [sig, arg]; - recordIndex++; - return sig; - } - }; - return {reset, swap, proxy}; -} - -export const GroupSignal = ProxyGroup(SignalsParts.signal); -export const GroupSignalHook = ProxyGroup(SignalsParts.useSignal); - - -const proxySignal = GroupSignal.proxy; -const proxySignalHook = GroupSignalHook.proxy; - -export * from "signals-original"; -export { proxySignal as signal, proxySignalHook as useSignal }; -export default {...SignalsParts, signal:proxySignal, useSignal:proxySignalHook}; \ No newline at end of file diff --git a/index.html b/index.html index f105cf8..07fe9d7 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,10 @@ - - - - Document - - - - + + + + Document + + + + \ No newline at end of file diff --git a/server.ts b/server.ts index bdb8c5e..72f82de 100644 --- a/server.ts +++ b/server.ts @@ -28,31 +28,61 @@ const bakeConfigPackage:Deno.bundle.Options = const bakeConfigLocal:Deno.bundle.Options = {...bakeConfigPackage, minify:false, sourcemap:"inline", inlineImports:false }; async function BakeForce(path:string, type?:"package") { - const config = type ? bakeConfigPackage : bakeConfigLocal; + // If already baking, return the in-flight promise. (Caller may also call BakeCheck which handles this.) + if (BakeCache[path] && typeof (BakeCache[path] as any)?.then === "function") + { + return await BakeCache[path] as CachedTranspile | undefined; + } + + // Create a fresh config per bake to avoid shared mutation. + const config = {...(type ? bakeConfigPackage : bakeConfigLocal)}; + config.entrypoints = [...config.entrypoints]; config.entrypoints[0] = type ? path : RootRunning+path; console.log("baking", config.entrypoints); - const result = await Deno.bundle(config); - if(result.outputFiles) + // store the in-flight promise immediately so concurrent callers reuse it + const inflight = (async () => { - const body = result.outputFiles.map(file=>file.text()).join("\n"); - const save:CachedTranspile = [body, type ? "" : ModuleProxy(body, path)]; - BakeCache[path] = save; - return save; - } - return undefined; + try + { + const result = await Deno.bundle(config); + if (result.outputFiles) + { + const body = result.outputFiles.map(file=>file.text()).join("\n"); + const save:CachedTranspile = [body, type ? "" : ModuleProxy(body, path)]; + BakeCache[path] = save; // replace promise with resolved value + return save; + } + } + catch (e) + { + console.log("BakeForce error for", path, e); + } + // failed - remove cache entry so next attempt can retry + delete BakeCache[path]; + return undefined; + })(); + + BakeCache[path] = inflight; + return await inflight; }; async function BakeCheck(path:string, type?:"package") { - const lookup:CachedTranspile = await BakeCache[path]; + const lookup = BakeCache[path]; if(!lookup) { return await BakeForce(path, type); } - return lookup; + // if an in-flight promise is stored, await it + if (typeof (lookup as any)?.then === "function") + { + return await lookup as CachedTranspile | undefined; + } + return lookup as CachedTranspile; } type CachedTranspile = [file:string, profile:string] -const BakeCache:Record = {} +// BakeCache may hold a resolved cached tuple or an in-flight Promise that resolves to one. +const BakeCache:Record | undefined> = {} const denoBody = await fetch(RootRunning+"/deno.json").then(resp=>resp.json()); @@ -78,7 +108,18 @@ const htmlPageHead = {headers:{"content-type":"text/html"}} const IndexResponse =()=> new Response(htmlPageBody, htmlPageHead); const JSHead = {headers:{"content-type":"application/javascript"}}; -const JSResponse =(body:string)=>new Response(body, JSHead); +const JSResponse =(body:string)=> +{ + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + start(controller) + { + controller.enqueue(encoder.encode(body)); + controller.close(); + } + }); + return new Response(stream, JSHead); +} Deno.serve(async(req:Request)=> { @@ -90,7 +131,7 @@ Deno.serve(async(req:Request)=> socket.onopen = () => SocketsLive.add(socket); socket.onclose = () => SocketsLive.delete(socket); socket.onmessage = () => {}; - socket.onerror = (e) => console.log("Socket errored:", e); + socket.onerror = (e) => {console.log("Socket errored:", e);SocketsLive.delete(socket);} return response; } catch(e){ console.log("Socket errored:", e); } @@ -99,7 +140,12 @@ Deno.serve(async(req:Request)=> const url = new URL(req.url); const parts = url.pathname.split("/").filter(part=>part); - const lastPart = parts.at(-1); + // if there are no path segments, serve index immediately (avoid calling extractExtension on undefined) + if(parts.length === 0) + { + return IndexResponse(); + } + const lastPart = parts.at(-1) ?? ""; const extension = extractExtension(lastPart); if(parts[0] == keyBundle) @@ -128,8 +174,33 @@ Deno.serve(async(req:Request)=> return new Response(); }); + + const SocketsLive:Set = new Set(); -const SocketsSend =(inData:string)=>{ for (const socket of SocketsLive){ socket.send(inData); } } +const SocketsSend =(inData:string)=> +{ + // iterate over a snapshot so we can remove while iterating + for (const socket of Array.from(SocketsLive)) + { + try + { + if (socket.readyState === WebSocket.OPEN) + { + socket.send(inData); + } + else + { + // not open any more — remove it + SocketsLive.delete(socket); + } + } + catch (err) + { + console.log("Failed to send to socket:", err); + SocketsLive.delete(socket); + } + } +} const Watcher =async()=> { @@ -143,10 +214,11 @@ const Watcher =async()=> blocking = true; setTimeout(async()=> { - for await (const [path, action] of filesChanged) + // filesChanged is a Map, iterate normally + for (const [path, action] of filesChanged) { const extension = extractExtension(path); - + if(keysExtension.includes(extension)) {