remove preact, GPT fixes

This commit is contained in:
Seth Trowbridge 2025-10-09 10:13:49 -04:00
parent 5bb8ba29a4
commit 102f8a74d2
7 changed files with 117 additions and 109 deletions

View File

@ -4,12 +4,11 @@ import React from "react";
export function App(){ export function App(){
console.log(React.useState(1))
const [countGet, countSet] = React.useState(0); const [countGet, countSet] = React.useState(2);
return <> return <>
<p style={{padding:"2rem", background:"blue", color:"white"}}>test paragraph</p> <p style={{padding:"2rem", background:"red", color:"white"}}>test paragraph</p>
<button onClick={()=>{countSet(countGet+1)}}>{countGet}</button> <button onClick={()=>{countSet(countGet+1)}}>{countGet}</button>
</> </>
} }

View File

@ -13,9 +13,6 @@
"react":"https://esm.sh/react@19.2.0", "react":"https://esm.sh/react@19.2.0",
"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/", "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" "react-original": "https://esm.sh/react@19.2.0"
} }
} }

View File

@ -1,26 +1,25 @@
{ {
"version": "5", "version": "5",
"specifiers": { "specifiers": {
"npm:@preact/signals@*": "2.3.2_preact@10.27.2" "jsr:@std/media-types@*": "1.1.0"
}, },
"npm": { "jsr": {
"@preact/signals-core@1.12.1": { "@std/media-types@1.1.0": {
"integrity": "sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==" "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
},
"@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=="
} }
}, },
"workspace": { "redirects": {
"dependencies": [ "https://esm.sh/scheduler@^0.27.0?target=denonext": "https://esm.sh/scheduler@0.27.0?target=denonext"
"npm:@preact/signals@*" },
] "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"
} }
} }

View File

@ -1,5 +1,4 @@
import { HMR } from "./hmr-react.tsx"; import { HMR } from "./hmr-react.tsx";
import { GroupSignal, GroupSignalHook } from "./hmr-signal.tsx";
const FileListeners = new Map() as Map<string, Array<(module:unknown)=>void>>; const FileListeners = new Map() as Map<string, Array<(module:unknown)=>void>>;
export const FileListen =(inPath:string, inHandler:()=>void)=> 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 // When a file changes, dynamically re-import it to get the updated members
// send the updated members to any listeners for that file // send the updated members to any listeners for that file
GroupSignal.reset();
const reImport = await import(document.location.origin+event.data+"?reload="+Math.random()); const reImport = await import(document.location.origin+event.data+"?reload="+Math.random());
FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport)); FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport));
GroupSignal.swap();
GroupSignalHook.reset();
HMR.update(); HMR.update();
GroupSignalHook.reset();
}); });
Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")}) Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")})
const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000);

View File

@ -1,47 +0,0 @@
import * as SignalsParts from "signals-original";
import DeepEqual from "https://esm.sh/deep-eql@4.1.3";
type Entry<T> = [signal:SignalsParts.Signal<T>, initArg:T];
function ProxyGroup<T>(inFunc:(initArg:T)=>SignalsParts.Signal<T>)
{
let recordEntry:Entry<T>[] = [];
let recordEntryNew:Entry<T>[] = [];
let recordIndex = 0;
const reset =()=> recordIndex = 0;
const swap =()=>
{
recordEntry = recordEntryNew;
recordEntryNew = [] as Entry<T>[];
};
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};

View File

@ -1,12 +1,10 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title> <title>Document</title>
</head> </head>
<body> <body>
<script type="module"> <script type="module" src="/app.tsx"></script>
import "/app.tsx"; </body>
</script>
</body>
</html> </html>

108
server.ts
View File

@ -28,31 +28,61 @@ const bakeConfigPackage:Deno.bundle.Options =
const bakeConfigLocal:Deno.bundle.Options = {...bakeConfigPackage, minify:false, sourcemap:"inline", inlineImports:false }; const bakeConfigLocal:Deno.bundle.Options = {...bakeConfigPackage, minify:false, sourcemap:"inline", inlineImports:false };
async function BakeForce(path:string, type?:"package") 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; config.entrypoints[0] = type ? path : RootRunning+path;
console.log("baking", config.entrypoints); 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"); try
const save:CachedTranspile = [body, type ? "" : ModuleProxy(body, path)]; {
BakeCache[path] = save; const result = await Deno.bundle(config);
return save; if (result.outputFiles)
} {
return undefined; 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") async function BakeCheck(path:string, type?:"package")
{ {
const lookup:CachedTranspile = await BakeCache[path]; const lookup = BakeCache[path];
if(!lookup) if(!lookup)
{ {
return await BakeForce(path, type); 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] type CachedTranspile = [file:string, profile:string]
const BakeCache:Record<string, CachedTranspile> = {} // BakeCache may hold a resolved cached tuple or an in-flight Promise that resolves to one.
const BakeCache:Record<string, CachedTranspile | Promise<CachedTranspile|undefined> | undefined> = {}
const denoBody = await fetch(RootRunning+"/deno.json").then(resp=>resp.json()); 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 IndexResponse =()=> new Response(htmlPageBody, htmlPageHead);
const JSHead = {headers:{"content-type":"application/javascript"}}; 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)=> Deno.serve(async(req:Request)=>
{ {
@ -90,7 +131,7 @@ Deno.serve(async(req:Request)=>
socket.onopen = () => SocketsLive.add(socket); socket.onopen = () => SocketsLive.add(socket);
socket.onclose = () => SocketsLive.delete(socket); socket.onclose = () => SocketsLive.delete(socket);
socket.onmessage = () => {}; socket.onmessage = () => {};
socket.onerror = (e) => console.log("Socket errored:", e); socket.onerror = (e) => {console.log("Socket errored:", e);SocketsLive.delete(socket);}
return response; return response;
} }
catch(e){ console.log("Socket errored:", e); } catch(e){ console.log("Socket errored:", e); }
@ -99,7 +140,12 @@ Deno.serve(async(req:Request)=>
const url = new URL(req.url); const url = new URL(req.url);
const parts = url.pathname.split("/").filter(part=>part); 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); const extension = extractExtension(lastPart);
if(parts[0] == keyBundle) if(parts[0] == keyBundle)
@ -128,8 +174,33 @@ Deno.serve(async(req:Request)=>
return new Response(); return new Response();
}); });
const SocketsLive:Set<WebSocket> = new Set(); const SocketsLive:Set<WebSocket> = 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()=> const Watcher =async()=>
{ {
@ -143,10 +214,11 @@ const Watcher =async()=>
blocking = true; blocking = true;
setTimeout(async()=> 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); const extension = extractExtension(path);
if(keysExtension.includes(extension)) if(keysExtension.includes(extension))
{ {