Compare commits

..

2 Commits

Author SHA1 Message Date
102f8a74d2 remove preact, GPT fixes 2025-10-09 10:13:49 -04:00
5bb8ba29a4 much better 2025-10-09 08:45:09 -04:00
10 changed files with 123 additions and 163 deletions

2
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,2 @@
{
}

12
app.tsx
View File

@ -1,15 +1,15 @@
import ReactDOM from "react-dom/client";
import React from "react";
console.log(React.useId)
export function App(){
//const [countGet, countSet] = React.useState(0);
const [countGet, countSet] = React.useState(2);
return <>
<div>lol hey 12121</div>
<p>more</p>
<p style={{padding:"2rem", background:"red", color:"white"}}>test paragraph</p>
<button onClick={()=>{countSet(countGet+1)}}>{countGet}</button>
</>
}
@ -19,7 +19,7 @@ export const Other =()=>{
export function Root()
{
return <div><h1>rooted</h1><App/><Other/></div>
}
ReactDOM.createRoot(document.body).render(<div><App/><Other/></div>);
ReactDOM.createRoot(document.body).render(<Root/>);

View File

@ -5,18 +5,15 @@
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"types": ["npm:@types/react", "npm:@types/react-dom"],
"lib": [
"deno.window", "dom", "dom.asynciterable"
]
},
"imports": {
"react":"npm:react",
"react-dom/client":"npm:react-dom/client",
"@preact/signals":"npm:@preact/signals",
"signals-original": "npm:@preact/signals",
"react-original": "npm:react"
"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-original": "https://esm.sh/react@19.2.0"
}
}

View File

@ -1,60 +1,25 @@
{
"version": "5",
"specifiers": {
"jsr:@std/media-types@*": "1.1.0",
"npm:@preact/signals@*": "2.3.2_preact@10.27.2",
"npm:react-dom@*": "19.2.0_react@19.2.0",
"npm:react@*": "19.2.0"
"jsr:@std/media-types@*": "1.1.0"
},
"jsr": {
"@std/media-types@1.1.0": {
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
}
},
"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=="
},
"react-dom@19.2.0_react@19.2.0": {
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"dependencies": [
"react",
"scheduler"
]
},
"react@19.2.0": {
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="
},
"scheduler@0.27.0": {
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="
}
},
"redirects": {
"https://esm.sh/type-detect@^4.0.0?target=denonext": "https://esm.sh/type-detect@4.1.0?target=denonext"
"https://esm.sh/scheduler@^0.27.0?target=denonext": "https://esm.sh/scheduler@0.27.0?target=denonext"
},
"remote": {
"https://esm.sh/deep-eql@4.1.3": "324a95b802d9f87b5ed66afdf079a0c47cd1cac3db59e9face0969be8eb980f7",
"https://esm.sh/deep-eql@4.1.3/denonext/deep-eql.mjs": "53319cc47b4be171d3a1aeeef9f3160a818e08b35baf9018cd14093f79e2910c",
"https://esm.sh/type-detect@4.1.0/denonext/type-detect.mjs": "ea850c5962bd47b0157c7e4cf38376cb7fb9fb3ad2438be0a724dbbadda5b94e",
"https://esm.sh/type-detect@4.1.0?target=denonext": "7257f955377cabc9a54bfa18f3bd16e12e40a090f25bf238299325d562e92fca"
},
"workspace": {
"dependencies": [
"npm:@preact/signals@*",
"npm:@types/react-dom@*",
"npm:@types/react@*",
"npm:react-dom@*",
"npm:react@*"
]
"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,6 +0,0 @@
import Default from "react-jsx-runtime-original";
const jsx = Default.jsx;
const jsxs = Default.jsxs;
const Fragment = Default.Fragment;
console.log("run is", Default);
export { jsx, jsxs, Fragment };

View File

@ -1,5 +1,4 @@
import { HMR } from "./hmr-react.tsx";
import { GroupSignal, GroupSignalHook } from "./hmr-signal.tsx";
const FileListeners = new Map() as Map<string, Array<(module:unknown)=>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
// 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);

View File

@ -174,6 +174,5 @@ const ProxyReducer =(inReducer:(inState:Storelike, inAction:string)=>Storelike,
};
export * from "react-original";
const Fragment = ReactParts.Fragment
export {ProxyCreate as createElement, ProxyCreate as jsx, Fragment, ProxyState as useState, ProxyReducer as useReducer };
export default {...ReactParts, createElement:ProxyCreate, jsx:ProxyCreate, Fragment, useState:ProxyState, useReducer:ProxyReducer};
export {ProxyCreate as createElement, ProxyState as useState, ProxyReducer as useReducer };
export default {...ReactParts, createElement:ProxyCreate, useState:ProxyState, useReducer:ProxyReducer};

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">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import "/app.tsx";
</script>
</body>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module" src="/app.tsx"></script>
</body>
</html>

118
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 };
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<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());
@ -65,19 +95,9 @@ for(const key in denoBody.imports)
}
}
denoBody.imports["react-jsx-runtime-original"] = `${denoBody.imports[denoBody.compilerOptions.jsxImportSource]}/jsx-runtime`;
denoBody.imports["react/jsx-runtime"] = "/^/hmr/hmr-jsx-runtime.tsx";
denoBody.imports["react-original"] = denoBody.imports[denoBody.compilerOptions.jsxImportSource];
denoBody.imports["react"] = "/^/hmr/hmr-react.tsx";
// denoBody.imports["react-original"] = denoBody.imports["react"];
// denoBody.imports["react-jsx-runtime-original"] = denoBody.imports[denoBody.compilerOptions.jsxImportSource]+"/jsx-runtime";
// denoBody.imports["signals-original"] = denoBody.imports["@preact/signals"];
// denoBody.imports["@preact/signals"] = "/^/hmr/hmr-signal.tsx";
// denoBody.imports["react"] = "/^/hmr/hmr-react.tsx";
// denoBody.imports["react/jsx-runtime"] = "/^/hmr/hmr-react.tsx";
console.log(denoBody.imports);
const importMap = `<script type="importmap">{"imports":${JSON.stringify(denoBody.imports)}}</script>`;
@ -88,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)=>
{
@ -100,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); }
@ -109,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)
@ -138,8 +174,33 @@ Deno.serve(async(req:Request)=>
return new Response();
});
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()=>
{
@ -153,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))
{