From 223bebe48db6f204171553733fa8ed79c0c35c13 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Tue, 6 Jun 2023 17:22:14 -0400 Subject: [PATCH 01/25] client started --- client.tsx | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ serve.tsx | 70 +++++++----------------------------------------------- xpile.tsx | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 62 deletions(-) create mode 100644 client.tsx create mode 100644 xpile.tsx diff --git a/client.tsx b/client.tsx new file mode 100644 index 0000000..cdc75c1 --- /dev/null +++ b/client.tsx @@ -0,0 +1,64 @@ +import * as HTTP from "https://deno.land/std@0.177.0/http/server.ts"; +import * as Transpile from "./xpile.tsx"; + +const dir = `file://${Deno.cwd().replaceAll("\\", "/")}`; + +const Sockets:Set = new Set(); +const SocketsBroadcast =(inData:string)=>{ console.log(inData); for (const socket of Sockets){ socket.send(inData); } } +const SocketsHandler = (_req:Request)=> +{ + if(_req.headers.get("upgrade") == "websocket") + { + try + { + const { response, socket } = Deno.upgradeWebSocket(_req); + socket.onopen = () => Sockets.add(socket); + socket.onclose = () => Sockets.delete(socket); + socket.onmessage = (e) => {}; + socket.onerror = (e) => console.log("Socket errored:", e); + return response; + } + catch(e) + { + // + return new Response(e); + } + } + return new Response(`websockets only`); +}; + +HTTP.serve(SocketsHandler, { port: 4444 }); + +const watcher =async()=> +{ + let blocking = false; + const filesChanged:Map = new Map(); + for await (const event of Deno.watchFs(Deno.cwd())) + { + event.paths.forEach( path => filesChanged.set(path, event.kind) ); + if(!blocking) + { + blocking = true; + setTimeout(async()=> + { + for await (const [path, action] of filesChanged) + { + const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); + if(action != "remove") + { + await Transpile.Fetch(dir+key, key, true); + SocketsBroadcast(key); + } + else + { + Transpile.Cache.delete(key); + } + } + filesChanged.clear(); + blocking = false; + } + , 1000); + } + } +} +watcher().then(()=>console.log("done watching")); \ No newline at end of file diff --git a/serve.tsx b/serve.tsx index 66660dd..fea1461 100644 --- a/serve.tsx +++ b/serve.tsx @@ -1,88 +1,34 @@ import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts"; import * as HTTP from "https://deno.land/std@0.177.0/http/server.ts"; -import * as SWCW from "https://esm.sh/@swc/wasm-web@1.3.62"; +import * as Transpile from "./xpile.tsx"; -type Configuration = {Proxy:string, Allow:string, Reset:string}; -type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string}; +type Configuration = {Proxy:string, Allow:string, Reset:string, Local:boolean}; +type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, Local?:boolean}; let Configure:Configuration = { Proxy: "", Allow: "*", - Reset: "/clear-cache" + Reset: "/clear-cache", + Local: false }; export default (config:ConfigurationArgs)=> Configure = {...Configure, ...config}; -const TranspileConfig:SWCW.Options = { - sourceMaps: true, - minify: true, - jsc: - { - minify: - { - compress: { unused: true }, - mangle: true - }, - parser: - { - syntax: "typescript", - tsx: true, - }, - transform: - { - react: { runtime: "automatic" } - } - }, -} -const TranspileCache:Map = new Map(); -const TranspileFetch =async(inPath:string)=> -{ - if(inPath.endsWith(".tsx") || inPath.endsWith(".jsx") || inPath.endsWith(".js") || inPath.endsWith(".mjs")) - { - const check = TranspileCache.get(inPath); - if(check) - { - return check; - } - else - { - try - { - const resp = await fetch(Configure.Proxy + inPath); - const text = await resp.text(); - const {code, map} = await SWCW.transform(text, TranspileConfig); - TranspileCache.set(inPath, code); - return code; - } - catch(e) - { - return null; - } - } - } - else - { - return false; - } -}; -await SWCW.default(); HTTP.serve(async(req: Request)=> { const url:URL = new URL(req.url); if(url.pathname === Configure.Reset) { - const size = TranspileCache.size; - TranspileCache.clear(); - return new Response(`cache cleared (${size} items)`); + return new Response(`cache cleared (${Transpile.Clear()} items)`); } - const lookup = await TranspileFetch(url.pathname); + const lookup = await Transpile.Fetch(Configure.Proxy + url.pathname); if(lookup === null) { // error - return new Response(`error (see console)`, {status:404, headers:{"content-type":"application/javascript", "Access-Control-Allow-Origin": Configure.Allow, charset:"utf-8"}}); + return new Response(`transpile error (see console)`, {status:404, headers:{"content-type":"application/javascript", "Access-Control-Allow-Origin": Configure.Allow, charset:"utf-8"}}); } else if(lookup === false) { diff --git a/xpile.tsx b/xpile.tsx new file mode 100644 index 0000000..1ec85da --- /dev/null +++ b/xpile.tsx @@ -0,0 +1,65 @@ +import * as SWCW from "https://esm.sh/@swc/wasm-web@1.3.62"; + +await SWCW.default(); + +export const Config:SWCW.Options = +{ + sourceMaps: false, + minify: true, + jsc: + { + minify: + { + compress: { unused: true }, + mangle: true + }, + parser: + { + syntax: "typescript", + tsx: true, + }, + transform: + { + react: { runtime: "automatic" } + } + }, +} +export const Cache:Map = new Map(); +export const Clear =()=> +{ + const size = Cache.size; + Cache.clear(); + return size; +}; +export const Fetch =async(inPath:string, inKey:string, inCheckCache=true)=> +{ + if(inPath.endsWith(".tsx") || inPath.endsWith(".jsx") || inPath.endsWith(".js") || inPath.endsWith(".mjs")) + { + const check = Cache.get(inPath); + if(check && inCheckCache) + { + return check; + } + else + { + + try + { + const resp = await fetch(inPath); + const text = await resp.text(); + const {code} = await SWCW.transform(text, Config); + Cache.set(inKey, code); + return code; + } + catch(e) + { + console.log(`xpile.tsx error. Key:${inKey} Path:${inPath} Error:"${e}"`); + return null; + } + } + } + else + { + return false; + } +}; \ No newline at end of file -- 2.34.1 From afd760dbfbd3ce30eb1e94f40b6040c3d10b85e1 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Tue, 6 Jun 2023 22:24:43 -0400 Subject: [PATCH 02/25] local started --- client.tsx | 64 ------------------------------------- local.tsx | 59 ++++++++++++++++++++++++++++++++++ serve.tsx | 94 +++++++++++++++++++++++++++++++++++++++++++++++------- xpile.tsx | 65 ------------------------------------- 4 files changed, 142 insertions(+), 140 deletions(-) delete mode 100644 client.tsx create mode 100644 local.tsx delete mode 100644 xpile.tsx diff --git a/client.tsx b/client.tsx deleted file mode 100644 index cdc75c1..0000000 --- a/client.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import * as HTTP from "https://deno.land/std@0.177.0/http/server.ts"; -import * as Transpile from "./xpile.tsx"; - -const dir = `file://${Deno.cwd().replaceAll("\\", "/")}`; - -const Sockets:Set = new Set(); -const SocketsBroadcast =(inData:string)=>{ console.log(inData); for (const socket of Sockets){ socket.send(inData); } } -const SocketsHandler = (_req:Request)=> -{ - if(_req.headers.get("upgrade") == "websocket") - { - try - { - const { response, socket } = Deno.upgradeWebSocket(_req); - socket.onopen = () => Sockets.add(socket); - socket.onclose = () => Sockets.delete(socket); - socket.onmessage = (e) => {}; - socket.onerror = (e) => console.log("Socket errored:", e); - return response; - } - catch(e) - { - // - return new Response(e); - } - } - return new Response(`websockets only`); -}; - -HTTP.serve(SocketsHandler, { port: 4444 }); - -const watcher =async()=> -{ - let blocking = false; - const filesChanged:Map = new Map(); - for await (const event of Deno.watchFs(Deno.cwd())) - { - event.paths.forEach( path => filesChanged.set(path, event.kind) ); - if(!blocking) - { - blocking = true; - setTimeout(async()=> - { - for await (const [path, action] of filesChanged) - { - const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); - if(action != "remove") - { - await Transpile.Fetch(dir+key, key, true); - SocketsBroadcast(key); - } - else - { - Transpile.Cache.delete(key); - } - } - filesChanged.clear(); - blocking = false; - } - , 1000); - } - } -} -watcher().then(()=>console.log("done watching")); \ No newline at end of file diff --git a/local.tsx b/local.tsx new file mode 100644 index 0000000..a6bf756 --- /dev/null +++ b/local.tsx @@ -0,0 +1,59 @@ +import * as HTTP from "https://deno.land/std@0.177.0/http/server.ts"; +import Setup, {Transpile} from "./serve.tsx"; + +const Directory = `file://${Deno.cwd().replaceAll("\\", "/")}`; +Transpile.Config.sourceMaps = "inline"; +Setup({Proxy:Directory}); + +const SocketsLive:Set = new Set(); +const SocketsSend =(inData:string)=>{ console.log(inData); for (const socket of SocketsLive){ socket.send(inData); } } +HTTP.serve((_req:Request)=> +{ + if(_req.headers.get("upgrade") == "websocket") + { + try + { + const { response, socket } = Deno.upgradeWebSocket(_req); + socket.onopen = () => SocketsLive.add(socket); + socket.onclose = () => SocketsLive.delete(socket); + socket.onmessage = (e) => {}; + socket.onerror = (e) => console.log("Socket errored:", e); + return response; + } + catch(e) + { + return new Response(e); + } + } + return new Response(`websockets only`); +}, { port: 4444 }); + +let blocking = false; +const filesChanged:Map = new Map(); +for await (const event of Deno.watchFs(Deno.cwd())) +{ + event.paths.forEach( path => filesChanged.set(path, event.kind) ); + if(!blocking) + { + blocking = true; + setTimeout(async()=> + { + for await (const [path, action] of filesChanged) + { + const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); + if(action != "remove") + { + const tsx = await Transpile.Fetch(Directory+key, key, true); + tsx && SocketsSend(key); + } + else + { + Transpile.Cache.delete(key); + } + } + filesChanged.clear(); + blocking = false; + } + , 1000); + } +} diff --git a/serve.tsx b/serve.tsx index fea1461..ab5fc68 100644 --- a/serve.tsx +++ b/serve.tsx @@ -1,20 +1,85 @@ import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts"; import * as HTTP from "https://deno.land/std@0.177.0/http/server.ts"; -import * as Transpile from "./xpile.tsx"; +import * as SWCW from "https://esm.sh/@swc/wasm-web@1.3.62"; -type Configuration = {Proxy:string, Allow:string, Reset:string, Local:boolean}; -type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, Local?:boolean}; - -let Configure:Configuration = +export type Configuration = {Proxy:string, Allow:string, Reset:string, Local:boolean}; +export type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, Local?:boolean}; +export let Configure:Configuration = { Proxy: "", Allow: "*", Reset: "/clear-cache", Local: false }; + export default (config:ConfigurationArgs)=> Configure = {...Configure, ...config}; +export const Transpile = { + Config: + { + sourceMaps: false, + minify: true, + jsc: + { + target:"es2017", + minify: + { + compress: { unused: true }, + mangle: true + }, + parser: + { + syntax: "typescript", + tsx: true, + }, + transform: + { + react: { runtime: "automatic" } + } + }, + } as SWCW.Options, + Cache: new Map() as Map, + Clear() + { + const size = this.Cache.size; + this.Cache.clear(); + return size; + }, + Fetch: async function(inPath:string, inKey:string, inCheckCache=true) + { + console.log("transpile", inPath) + if(inPath.endsWith(".tsx") || inPath.endsWith(".jsx") || inPath.endsWith(".js") || inPath.endsWith(".mjs")) + { + const check = this.Cache.get(inPath); + if(check && inCheckCache) + { + return check; + } + else + { + try + { + const resp = await fetch(inPath); + const text = await resp.text(); + const {code} = await SWCW.transform(text, {...this.Config, filename:inKey}); + this.Cache.set(inKey, code); + return code; + } + catch(e) + { + console.log(`xpile.tsx error. Key:${inKey} Path:${inPath} Error:"${e}"`); + return null; + } + } + } + else + { + return false; + } + } +}; - +SWCW.default(); +console.log("starting server"); HTTP.serve(async(req: Request)=> { const url:URL = new URL(req.url); @@ -24,7 +89,7 @@ HTTP.serve(async(req: Request)=> return new Response(`cache cleared (${Transpile.Clear()} items)`); } - const lookup = await Transpile.Fetch(Configure.Proxy + url.pathname); + const lookup = await Transpile.Fetch(Configure.Proxy + url.pathname, url.pathname); if(lookup === null) { // error @@ -33,10 +98,17 @@ HTTP.serve(async(req: Request)=> else if(lookup === false) { // not a javascript file - const type = MIME.typeByExtension(url.pathname.substring(url.pathname.lastIndexOf("."))) || "text/html"; - const file = await fetch(Configure.Proxy + url.pathname); - const text = await file.text(); - return new Response(text, {headers:{"content-type":type, "Access-Control-Allow-Origin":Configure.Allow, charset:"utf-8"}}); + try + { + const type = MIME.typeByExtension(url.pathname.substring(url.pathname.lastIndexOf("."))) || "text/html"; + const file = await fetch(Configure.Proxy + url.pathname); + const text = await file.text(); + return new Response(text, {headers:{"content-type":type, "Access-Control-Allow-Origin":Configure.Allow, charset:"utf-8"}}); + } + catch(e) + { + return new Response(`404 ${Configure.Proxy + url.pathname}`, {status:404}); + } } else { diff --git a/xpile.tsx b/xpile.tsx deleted file mode 100644 index 1ec85da..0000000 --- a/xpile.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import * as SWCW from "https://esm.sh/@swc/wasm-web@1.3.62"; - -await SWCW.default(); - -export const Config:SWCW.Options = -{ - sourceMaps: false, - minify: true, - jsc: - { - minify: - { - compress: { unused: true }, - mangle: true - }, - parser: - { - syntax: "typescript", - tsx: true, - }, - transform: - { - react: { runtime: "automatic" } - } - }, -} -export const Cache:Map = new Map(); -export const Clear =()=> -{ - const size = Cache.size; - Cache.clear(); - return size; -}; -export const Fetch =async(inPath:string, inKey:string, inCheckCache=true)=> -{ - if(inPath.endsWith(".tsx") || inPath.endsWith(".jsx") || inPath.endsWith(".js") || inPath.endsWith(".mjs")) - { - const check = Cache.get(inPath); - if(check && inCheckCache) - { - return check; - } - else - { - - try - { - const resp = await fetch(inPath); - const text = await resp.text(); - const {code} = await SWCW.transform(text, Config); - Cache.set(inKey, code); - return code; - } - catch(e) - { - console.log(`xpile.tsx error. Key:${inKey} Path:${inPath} Error:"${e}"`); - return null; - } - } - } - else - { - return false; - } -}; \ No newline at end of file -- 2.34.1 From 083e37a1736ab4c87aa87132fc0d72cabf6e9a00 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Tue, 6 Jun 2023 22:48:45 -0400 Subject: [PATCH 03/25] example started --- example/deno.json | 6 ++++ hmr/hmr.tsx | 76 ++++++++++++++++++++++++++++++++++++++++++++++ hmr/react.tsx | 77 +++++++++++++++++++++++++++++++++++++++++++++++ serve.tsx | 4 +++ 4 files changed, 163 insertions(+) create mode 100644 example/deno.json create mode 100644 hmr/hmr.tsx create mode 100644 hmr/react.tsx diff --git a/example/deno.json b/example/deno.json new file mode 100644 index 0000000..16d929c --- /dev/null +++ b/example/deno.json @@ -0,0 +1,6 @@ +{ + "imports": + { + "react":"https://esm.sh/preact/compat" + } +} \ No newline at end of file diff --git a/hmr/hmr.tsx b/hmr/hmr.tsx new file mode 100644 index 0000000..198981e --- /dev/null +++ b/hmr/hmr.tsx @@ -0,0 +1,76 @@ + +let reloads = 0; +const listeners = new Map() as Mapvoid>>; +const socket:WebSocket = new WebSocket("ws://"+document.location.host); +socket.addEventListener('message', (event) => +{ + let handlers = listeners.get(event.data)??[]; + reloads++; + Promise.all( + handlers.map(handler=> + { + return import(event.data+"?reload="+reloads) + .then(updatedModule=>handler(updatedModule)); + }) + ).then(()=>HMR.update()); +}); +const socketTimer = setInterval(()=>{socket.send("ping")}, 1000); + +export const FileListen =(inPath:string, inHandler:()=>void)=> +{ + const members = listeners.get(inPath)??[]; + members.push(inHandler); + listeners.set(inPath, members); +}; + +const HMR = { + reloads:0, + registered: new Map() as Mapvoid>, + states: new Map(), + statesOld: new Map(), + wireframe: false, + onChange(key:string, value:()=>void):void + { + this.registered.set(key, value); + }, + update() + { + this.reloads++; + this.registered.forEach(handler=>handler()); + this.registered.clear(); + this.statesOld = this.states; + this.states = new Map(); + this.echoState(); + }, + echoState() + { + let output = []; + for(const[key, val] of HMR.statesOld) + { + output[key] = val.state+"--"+val.reload; + } + console.log(output); + output = []; + for(const[key, val] of HMR.states) + { + output[key] = val.state+"--"+val.reload; + } + console.log(output); + } +}; + +export {HMR}; + +export const MapAt =(inMap, inIndex)=> +{ + let index = 0; + for(const kvp of inMap) + { + if(index == inIndex) + { + return kvp; + } + index++; + } + return false; +}; \ No newline at end of file diff --git a/hmr/react.tsx b/hmr/react.tsx new file mode 100644 index 0000000..3b013cd --- /dev/null +++ b/hmr/react.tsx @@ -0,0 +1,77 @@ +import * as ReactParts from "react-original"; +import { HMR, MapAt } from "./hmr.tsx"; + +const H = ReactParts.createElement; + +const ProxyElement = (props)=> +{ + const id = ReactParts.useId(); + const [stateGet, stateSet] = ReactParts.useState(0); + ReactParts.useEffect(()=>HMR.onChange(id, ()=>stateSet(stateGet+1))); + + const child = H(...props.__args); + + if(HMR.wireframe) + { + return H("div", {style:{padding:"10px", border:"2px solid red"}}, + H("p", null, stateGet), + child + ); + } + else + { + return child; + } +}; + +const ProxyCreate =(...args)=> +{ + return typeof args[0] != "string" ? H(ProxyElement, {__args:args, ...args[1]}) : H(...args); +}; + +const ProxyState =(arg)=> +{ + const id = ReactParts.useId(); + const trueArg = arg; + + // does statesOld have an entry for this state? use that instead of the passed arg + const check = MapAt(HMR.statesOld, HMR.states.size); + if(check) + { + arg = check[1].state; + console.info(`BOOTING with ${arg}`); + } + + const lastKnowReloads = HMR.reloads; + const [stateGet, stateSet] = ReactParts.useState(arg); + ReactParts.useEffect(()=>{ + return ()=>{ + if(HMR.reloads == lastKnowReloads) + { + // this is a switch/ui change, not a HMR reload change + const oldState = MapAt(HMR.statesOld, HMR.states.size-1); + HMR.statesOld.set(oldState[0], {...oldState[1], state:trueArg}); + } + HMR.states.delete(id); + } + }, []); + + if(!HMR.states.has(id)) + { + HMR.states.set(id, {state:arg, set:stateSet, reload:HMR.reloads}); + } + + function proxySetter (arg) + { + //console.log("state spy update", id, arg); + HMR.states.set(id, {state:arg, set:stateSet, reload:HMR.reloads}); + return stateSet(arg); + } + return [stateGet, proxySetter]; + +}; + +export * from "react-original"; +export { ProxyCreate as createElement, ProxyState as useState }; +export const isProxy = true; +export default {...ReactParts.default, createElement:ProxyCreate, useState:ProxyState, isProxy:true}; \ No newline at end of file diff --git a/serve.tsx b/serve.tsx index ab5fc68..860e4c3 100644 --- a/serve.tsx +++ b/serve.tsx @@ -84,6 +84,10 @@ HTTP.serve(async(req: Request)=> { const url:URL = new URL(req.url); + if(url.pathname.endsWith("/")) + { + + } if(url.pathname === Configure.Reset) { return new Response(`cache cleared (${Transpile.Clear()} items)`); -- 2.34.1 From c758f66b271ac397c187ab75d1f0581dd89072c6 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Wed, 7 Jun 2023 11:22:49 -0400 Subject: [PATCH 04/25] more configuration --- local.tsx | 98 ++++++++++++++++++++++++++++----------- serve.tsx | 136 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 148 insertions(+), 86 deletions(-) diff --git a/local.tsx b/local.tsx index a6bf756..dbd2d56 100644 --- a/local.tsx +++ b/local.tsx @@ -1,32 +1,71 @@ -import * as HTTP from "https://deno.land/std@0.177.0/http/server.ts"; -import Setup, {Transpile} from "./serve.tsx"; - -const Directory = `file://${Deno.cwd().replaceAll("\\", "/")}`; -Transpile.Config.sourceMaps = "inline"; -Setup({Proxy:Directory}); +import Setup, {Transpile, Extension} from "./serve.tsx"; const SocketsLive:Set = new Set(); const SocketsSend =(inData:string)=>{ console.log(inData); for (const socket of SocketsLive){ socket.send(inData); } } -HTTP.serve((_req:Request)=> -{ - if(_req.headers.get("upgrade") == "websocket") +const Directory = `file://${Deno.cwd().replaceAll("\\", "/")}`; + +Setup({ + Proxy:Directory, + SWCOp: { - try + sourceMaps: "inline", + minify: false, + jsc: { - const { response, socket } = Deno.upgradeWebSocket(_req); - socket.onopen = () => SocketsLive.add(socket); - socket.onclose = () => SocketsLive.delete(socket); - socket.onmessage = (e) => {}; - socket.onerror = (e) => console.log("Socket errored:", e); - return response; + target:"es2022", + parser: + { + syntax: "typescript", + tsx: true, + } } - catch(e) + }, + async Serve(inReq, inURL, inExt) + { + if(inReq.headers.get("upgrade") == "websocket") { - return new Response(e); + try + { + const { response, socket } = Deno.upgradeWebSocket(inReq); + socket.onopen = () => SocketsLive.add(socket); + socket.onclose = () => SocketsLive.delete(socket); + socket.onmessage = (e) => {}; + socket.onerror = (e) => console.log("Socket errored:", e); + return response; + } + catch(e) + { + return new Response(e); + } } + + if(!inExt) + { + await fetch(Directory+"/deno.json"); + + return new Response( +` + + + + +
+ + + +`, {headers:{"content-type":"text/html"}}); + } + + return false; } - return new Response(`websockets only`); -}, { port: 4444 }); +}); let blocking = false; const filesChanged:Map = new Map(); @@ -40,15 +79,18 @@ for await (const event of Deno.watchFs(Deno.cwd())) { for await (const [path, action] of filesChanged) { - const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); - if(action != "remove") - { - const tsx = await Transpile.Fetch(Directory+key, key, true); - tsx && SocketsSend(key); - } - else + if(Transpile.Check(Extension(path))) { - Transpile.Cache.delete(key); + const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); + if(action != "remove") + { + const tsx = await Transpile.Fetch(Directory+key, key, true, true); + tsx && SocketsSend(key); + } + else + { + Transpile.Cache.delete(key); + } } } filesChanged.clear(); diff --git a/serve.tsx b/serve.tsx index 860e4c3..8c69e79 100644 --- a/serve.tsx +++ b/serve.tsx @@ -2,19 +2,16 @@ import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts"; import * as HTTP from "https://deno.land/std@0.177.0/http/server.ts"; import * as SWCW from "https://esm.sh/@swc/wasm-web@1.3.62"; -export type Configuration = {Proxy:string, Allow:string, Reset:string, Local:boolean}; -export type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, Local?:boolean}; -export let Configure:Configuration = +type CustomHTTPHandler = (inReq:Request, inURL:URL, inExtension:string|false)=>false|Response|Promise +type Configuration = {Proxy:string, Allow:string, Reset:string, SWCOp:SWCW.Options, Serve:CustomHTTPHandler}; +type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, SWCOp?:SWCW.Options, Serve?:CustomHTTPHandler}; +let Configure:Configuration = { Proxy: "", Allow: "*", Reset: "/clear-cache", - Local: false -}; - -export default (config:ConfigurationArgs)=> Configure = {...Configure, ...config}; -export const Transpile = { - Config: + Serve: ()=>false, + SWCOp: { sourceMaps: false, minify: true, @@ -35,9 +32,19 @@ export const Transpile = { { react: { runtime: "automatic" } } - }, - } as SWCW.Options, + } + } +}; +export default (config:ConfigurationArgs)=> Configure = {...Configure, ...config}; + +export const Transpile = +{ Cache: new Map() as Map, + Files: ["tsx", "jsx", "ts", "js", "mjs"], + Check(inExtension:string|false) + { + return inExtension ? this.Files.includes(inExtension) : false; + }, Clear() { const size = this.Cache.size; @@ -46,76 +53,89 @@ export const Transpile = { }, Fetch: async function(inPath:string, inKey:string, inCheckCache=true) { - console.log("transpile", inPath) - if(inPath.endsWith(".tsx") || inPath.endsWith(".jsx") || inPath.endsWith(".js") || inPath.endsWith(".mjs")) + const check = this.Cache.get(inPath); + if(check && inCheckCache) { - const check = this.Cache.get(inPath); - if(check && inCheckCache) - { - return check; - } - else - { - try - { - const resp = await fetch(inPath); - const text = await resp.text(); - const {code} = await SWCW.transform(text, {...this.Config, filename:inKey}); - this.Cache.set(inKey, code); - return code; - } - catch(e) - { - console.log(`xpile.tsx error. Key:${inKey} Path:${inPath} Error:"${e}"`); - return null; - } - } + return check; } else { - return false; + try + { + const resp = await fetch(inPath); + const text = await resp.text(); + const {code} = await SWCW.transform(text, { ...Configure.SWCOp, filename:inKey}); + this.Cache.set(inKey, code); + return code; + } + catch(e) + { + //console.log(`Transpile.Fetch error. Key:"${inKey}" Path:"${inPath}" Error:"${e}"`); + return null; + } } } }; +export const Extension =(inPath:string)=> +{ + const posSlash = inPath.lastIndexOf("/"); + const posDot = inPath.lastIndexOf("."); + return posDot > posSlash ? inPath.substring(posDot+1).toLowerCase() : false; +}; + SWCW.default(); -console.log("starting server"); HTTP.serve(async(req: Request)=> { const url:URL = new URL(req.url); + const ext = Extension(url.pathname); + const headers = {"content-type":"application/json", "Access-Control-Allow-Origin": Configure.Allow, "charset":"UTF-8"}; - if(url.pathname.endsWith("/")) + // allow for custom handler + const custom = await Configure.Serve(req, url, ext); + if(custom) { - + return custom; } + + // implied index.html files + if(!ext) + { + return new Response(` + + + + + + + +`, {headers:{...headers, "content-type":"text/html"}}); + } + + // cache-reset route if(url.pathname === Configure.Reset) { - return new Response(`cache cleared (${Transpile.Clear()} items)`); + return new Response(`{"cleared":${Transpile.Clear()}}`, {headers}); } - const lookup = await Transpile.Fetch(Configure.Proxy + url.pathname, url.pathname); - if(lookup === null) + // transpileable files + if(Transpile.Check(ext)) { - // error - return new Response(`transpile error (see console)`, {status:404, headers:{"content-type":"application/javascript", "Access-Control-Allow-Origin": Configure.Allow, charset:"utf-8"}}); + const lookup = await Transpile.Fetch(Configure.Proxy + url.pathname, url.pathname); + return new Response(lookup, {status:lookup?200:404, headers:{...headers, "content-type":"application/javascript"}} ); } - else if(lookup === false) + + // all other static files + try { - // not a javascript file - try - { - const type = MIME.typeByExtension(url.pathname.substring(url.pathname.lastIndexOf("."))) || "text/html"; - const file = await fetch(Configure.Proxy + url.pathname); - const text = await file.text(); - return new Response(text, {headers:{"content-type":type, "Access-Control-Allow-Origin":Configure.Allow, charset:"utf-8"}}); - } - catch(e) - { - return new Response(`404 ${Configure.Proxy + url.pathname}`, {status:404}); - } + const type = MIME.typeByExtension(ext); + const file = await fetch(Configure.Proxy + url.pathname); + const text = await file.text(); + return new Response(text, {headers:{...headers, "content-type":type||""}}); } - else + catch(e) { - return new Response(lookup, {headers:{"content-type":"application/javascript", "Access-Control-Allow-Origin": Configure.Allow, charset:"utf-8"}}); + return new Response(`{"error":"${e}", "path":"${url.pathname}"}`, {status:404, headers}); } + }); \ No newline at end of file -- 2.34.1 From 0aa0bb29558924bde57014dab737efebc3c569dc Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Wed, 7 Jun 2023 14:15:57 -0400 Subject: [PATCH 05/25] more configuration --- example/app.tsx | 5 +++ example/deno.json | 3 +- local.tsx | 34 ++++++++---------- serve.tsx | 91 +++++++++++++++++++++++++++++------------------ 4 files changed, 78 insertions(+), 55 deletions(-) create mode 100644 example/app.tsx diff --git a/example/app.tsx b/example/app.tsx new file mode 100644 index 0000000..03c2ce0 --- /dev/null +++ b/example/app.tsx @@ -0,0 +1,5 @@ +import React from "react"; +export default ()=> +{ + return

hey!

+} \ No newline at end of file diff --git a/example/deno.json b/example/deno.json index 16d929c..4d1522b 100644 --- a/example/deno.json +++ b/example/deno.json @@ -1,6 +1,7 @@ { "imports": { - "react":"https://esm.sh/preact/compat" + "react":"https://esm.sh/preact/compat", + "app": "./app.tsx" } } \ No newline at end of file diff --git a/local.tsx b/local.tsx index dbd2d56..cbafb77 100644 --- a/local.tsx +++ b/local.tsx @@ -1,10 +1,10 @@ -import Setup, {Transpile, Extension} from "./serve.tsx"; +import Configure, {Transpile, Extension} from "./serve.tsx"; const SocketsLive:Set = new Set(); const SocketsSend =(inData:string)=>{ console.log(inData); for (const socket of SocketsLive){ socket.send(inData); } } const Directory = `file://${Deno.cwd().replaceAll("\\", "/")}`; -Setup({ +Configure({ Proxy:Directory, SWCOp: { @@ -20,7 +20,7 @@ Setup({ } } }, - async Serve(inReq, inURL, inExt) + Serve(inReq, inURL, inExt, inMap) { if(inReq.headers.get("upgrade") == "websocket") { @@ -38,31 +38,27 @@ Setup({ return new Response(e); } } - + if(!inExt) { - await fetch(Directory+"/deno.json"); - - return new Response( -` + return new Response +(`
- + - -`, {headers:{"content-type":"text/html"}}); + +`, {status:200, headers:{"content-type":"text/html"}}); } - + return false; } }); @@ -84,7 +80,7 @@ for await (const event of Deno.watchFs(Deno.cwd())) const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); if(action != "remove") { - const tsx = await Transpile.Fetch(Directory+key, key, true, true); + const tsx = await Transpile.Fetch(Directory+key, key, true); tsx && SocketsSend(key); } else diff --git a/serve.tsx b/serve.tsx index 8c69e79..3a09418 100644 --- a/serve.tsx +++ b/serve.tsx @@ -2,15 +2,42 @@ import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts"; import * as HTTP from "https://deno.land/std@0.177.0/http/server.ts"; import * as SWCW from "https://esm.sh/@swc/wasm-web@1.3.62"; -type CustomHTTPHandler = (inReq:Request, inURL:URL, inExtension:string|false)=>false|Response|Promise -type Configuration = {Proxy:string, Allow:string, Reset:string, SWCOp:SWCW.Options, Serve:CustomHTTPHandler}; -type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, SWCOp?:SWCW.Options, Serve?:CustomHTTPHandler}; +const ImportMap = {imports:{}}; +const ImportMapReload =async()=> +{ + let confText; + try + { + confText = await Deno.readTextFile(Deno.cwd()+"\\deno.json"); + } + catch(e) + { + console.log(`No "deno.json" file found at "${Deno.cwd()}"`); + } + confText && (ImportMap.imports = Configure.Remap(JSON.parse(confText)?.imports || {})); +}; + +type CustomHTTPHandler = (inReq:Request, inURL:URL, inExt:string|false, inMap:{imports:Record})=>false|Response|Promise; +type CustomRemapper = (inImports:Record)=>Record; +type Configuration = {Proxy:string, Allow:string, Reset:string, SWCOp:SWCW.Options, Serve:CustomHTTPHandler, Remap:CustomRemapper}; +type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, SWCOp?:SWCW.Options, Serve?:CustomHTTPHandler, Remap?:CustomRemapper}; let Configure:Configuration = { Proxy: "", Allow: "*", Reset: "/clear-cache", Serve: ()=>false, + Remap: (inImports)=> + { + Object.entries(inImports).forEach(([key, value])=> + { + if(value.startsWith("./")) + { + inImports[key] = value.substring(1); + } + }); + return inImports; + }, SWCOp: { sourceMaps: false, @@ -35,7 +62,6 @@ let Configure:Configuration = } } }; -export default (config:ConfigurationArgs)=> Configure = {...Configure, ...config}; export const Transpile = { @@ -49,6 +75,7 @@ export const Transpile = { const size = this.Cache.size; this.Cache.clear(); + ImportMapReload(); return size; }, Fetch: async function(inPath:string, inKey:string, inCheckCache=true) @@ -84,40 +111,29 @@ export const Extension =(inPath:string)=> return posDot > posSlash ? inPath.substring(posDot+1).toLowerCase() : false; }; -SWCW.default(); +export default (config:ConfigurationArgs)=> Configure = {...Configure, ...config}; + +await ImportMapReload(); +await SWCW.default(); HTTP.serve(async(req: Request)=> { const url:URL = new URL(req.url); const ext = Extension(url.pathname); const headers = {"content-type":"application/json", "Access-Control-Allow-Origin": Configure.Allow, "charset":"UTF-8"}; - // allow for custom handler - const custom = await Configure.Serve(req, url, ext); - if(custom) - { - return custom; - } - - // implied index.html files - if(!ext) - { - return new Response(` - - - - - - - -`, {headers:{...headers, "content-type":"text/html"}}); - } - // cache-reset route if(url.pathname === Configure.Reset) { return new Response(`{"cleared":${Transpile.Clear()}}`, {headers}); } + // allow for custom handler + const custom = await Configure.Serve(req, url, ext, ImportMap); + if(custom) + { + return custom; + } + // transpileable files if(Transpile.Check(ext)) { @@ -126,16 +142,21 @@ HTTP.serve(async(req: Request)=> } // all other static files - try + if(ext) { - const type = MIME.typeByExtension(ext); - const file = await fetch(Configure.Proxy + url.pathname); - const text = await file.text(); - return new Response(text, {headers:{...headers, "content-type":type||""}}); - } - catch(e) - { - return new Response(`{"error":"${e}", "path":"${url.pathname}"}`, {status:404, headers}); + try + { + const type = MIME.typeByExtension(ext); + const file = await fetch(Configure.Proxy + url.pathname); + const text = await file.text(); + return new Response(text, {headers:{...headers, "content-type":type||""}}); + } + catch(e) + { + return new Response(`{"error":"${e}", "path":"${url.pathname}"}`, {status:404, headers}); + } } + return new Response(`{"error":"unmatched route", "path":"${url.pathname}"}`, {status:404, headers}); + }); \ No newline at end of file -- 2.34.1 From 0b0396b58dc608668792f7f3a41379c2822e6992 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Wed, 7 Jun 2023 17:09:40 -0400 Subject: [PATCH 06/25] add Shell phase --- deno.json | 8 +++++++ example/deno.json | 3 ++- local.tsx | 25 ++++---------------- serve.tsx | 58 ++++++++++++++++++++++++++++++++++++----------- 4 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 deno.json diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..a5c402c --- /dev/null +++ b/deno.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { "lib": ["deno.window", "dom"]}, + "imports": + { + "react":"https://esm.sh/preact@10.13.2/compat", + "app": "./app.tsx" + } +} \ No newline at end of file diff --git a/example/deno.json b/example/deno.json index 4d1522b..9a61d59 100644 --- a/example/deno.json +++ b/example/deno.json @@ -1,7 +1,8 @@ { + "compilerOptions": { "lib": ["deno.window", "dom"] }, "imports": { - "react":"https://esm.sh/preact/compat", + "react":"https://esm.sh/preact@10.13.2/compat", "app": "./app.tsx" } } \ No newline at end of file diff --git a/local.tsx b/local.tsx index cbafb77..9affb83 100644 --- a/local.tsx +++ b/local.tsx @@ -1,4 +1,4 @@ -import Configure, {Transpile, Extension} from "./serve.tsx"; +import {Configure, Transpile, Extension} from "./serve.tsx"; const SocketsLive:Set = new Set(); const SocketsSend =(inData:string)=>{ console.log(inData); for (const socket of SocketsLive){ socket.send(inData); } } @@ -20,6 +20,7 @@ Configure({ } } }, + Serve(inReq, inURL, inExt, inMap) { if(inReq.headers.get("upgrade") == "websocket") @@ -38,28 +39,10 @@ Configure({ return new Response(e); } } - - if(!inExt) + else { - return new Response -(` - - - - -
- - - - -`, {status:200, headers:{"content-type":"text/html"}}); + return false; } - - return false; } }); diff --git a/serve.tsx b/serve.tsx index 3a09418..a7dbacd 100644 --- a/serve.tsx +++ b/serve.tsx @@ -14,19 +14,22 @@ const ImportMapReload =async()=> { console.log(`No "deno.json" file found at "${Deno.cwd()}"`); } - confText && (ImportMap.imports = Configure.Remap(JSON.parse(confText)?.imports || {})); + confText && (ImportMap.imports = Configuration.Remap(JSON.parse(confText)?.imports || {})); }; type CustomHTTPHandler = (inReq:Request, inURL:URL, inExt:string|false, inMap:{imports:Record})=>false|Response|Promise; type CustomRemapper = (inImports:Record)=>Record; -type Configuration = {Proxy:string, Allow:string, Reset:string, SWCOp:SWCW.Options, Serve:CustomHTTPHandler, Remap:CustomRemapper}; -type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, SWCOp?:SWCW.Options, Serve?:CustomHTTPHandler, Remap?:CustomRemapper}; -let Configure:Configuration = +type Configuration = {Proxy:string, Allow:string, Reset:string, SWCOp:SWCW.Options, Serve:CustomHTTPHandler, Shell:CustomHTTPHandler, Remap:CustomRemapper}; +type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, SWCOp?:SWCW.Options, Serve?:CustomHTTPHandler, Shell?:CustomHTTPHandler, Remap?:CustomRemapper}; +let Configuration:Configuration = { - Proxy: "", + Proxy: `file://${Deno.cwd().replaceAll("\\", "/")}`, Allow: "*", Reset: "/clear-cache", - Serve: ()=>false, + Serve(inReq, inURL, inExt, inMap) + { + return false; + }, Remap: (inImports)=> { Object.entries(inImports).forEach(([key, value])=> @@ -36,8 +39,27 @@ let Configure:Configuration = inImports[key] = value.substring(1); } }); + Configuration.SWCOp.jsc.transform.react.importSource = inImports["react"]; return inImports; }, + Shell(inReq, inURL, inExt, inMap) + { + return new Response( + ` + + + + +
+ + + + `, {status:200, headers:{"content-type":"text/html"}}); + }, SWCOp: { sourceMaps: false, @@ -91,7 +113,7 @@ export const Transpile = { const resp = await fetch(inPath); const text = await resp.text(); - const {code} = await SWCW.transform(text, { ...Configure.SWCOp, filename:inKey}); + const {code} = await SWCW.transform(text, { ...Configuration.SWCOp, filename:inKey}); this.Cache.set(inKey, code); return code; } @@ -111,7 +133,7 @@ export const Extension =(inPath:string)=> return posDot > posSlash ? inPath.substring(posDot+1).toLowerCase() : false; }; -export default (config:ConfigurationArgs)=> Configure = {...Configure, ...config}; +export const Configure =(config:ConfigurationArgs)=> Configuration = {...Configuration, ...config}; await ImportMapReload(); await SWCW.default(); @@ -119,25 +141,35 @@ HTTP.serve(async(req: Request)=> { const url:URL = new URL(req.url); const ext = Extension(url.pathname); - const headers = {"content-type":"application/json", "Access-Control-Allow-Origin": Configure.Allow, "charset":"UTF-8"}; + const headers = {"content-type":"application/json", "Access-Control-Allow-Origin": Configuration.Allow, "charset":"UTF-8"}; // cache-reset route - if(url.pathname === Configure.Reset) + if(url.pathname === Configuration.Reset) { return new Response(`{"cleared":${Transpile.Clear()}}`, {headers}); } // allow for custom handler - const custom = await Configure.Serve(req, url, ext, ImportMap); + const custom = await Configuration.Serve(req, url, ext, ImportMap); if(custom) { return custom; } + // custom page html + if(!ext) + { + const shell = await Configuration.Shell(req, url, ext, ImportMap); + if(shell) + { + return shell; + } + } + // transpileable files if(Transpile.Check(ext)) { - const lookup = await Transpile.Fetch(Configure.Proxy + url.pathname, url.pathname); + const lookup = await Transpile.Fetch(Configuration.Proxy + url.pathname, url.pathname); return new Response(lookup, {status:lookup?200:404, headers:{...headers, "content-type":"application/javascript"}} ); } @@ -147,7 +179,7 @@ HTTP.serve(async(req: Request)=> try { const type = MIME.typeByExtension(ext); - const file = await fetch(Configure.Proxy + url.pathname); + const file = await fetch(Configuration.Proxy + url.pathname); const text = await file.text(); return new Response(text, {headers:{...headers, "content-type":type||""}}); } -- 2.34.1 From 6878e7e0a48f5457b58b3ae24855182fc2ef757c Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Wed, 7 Jun 2023 23:35:00 -0400 Subject: [PATCH 07/25] misc fixes --- .vscode/settings.json | 3 ++- deno.json | 7 +++++-- example/app.tsx | 3 ++- example/deno.json | 2 +- example/deno.lock | 11 +++++++++++ local.tsx | 17 +++++++++++------ serve.tsx | 33 +++++++++++++++++++++------------ 7 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 example/deno.lock diff --git a/.vscode/settings.json b/.vscode/settings.json index 8675ad5..b003be2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "deno.enable": true, - "deno.unstable": true + "deno.unstable": true, + "deno.config": "./deno.json" } \ No newline at end of file diff --git a/deno.json b/deno.json index a5c402c..9a94b43 100644 --- a/deno.json +++ b/deno.json @@ -1,8 +1,11 @@ { - "compilerOptions": { "lib": ["deno.window", "dom"]}, + "compilerOptions": { "lib": ["deno.window", "dom"], + "jsx": "react-jsx", + "jsxImportSource": "https://esm.sh/preact@10.15.1" + }, "imports": { - "react":"https://esm.sh/preact@10.13.2/compat", + "react":"https://esm.sh/preact@10.15.1/compat", "app": "./app.tsx" } } \ No newline at end of file diff --git a/example/app.tsx b/example/app.tsx index 03c2ce0..ed9b503 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -1,4 +1,5 @@ -import React from "react"; + + export default ()=> { return

hey!

diff --git a/example/deno.json b/example/deno.json index 9a61d59..8256efb 100644 --- a/example/deno.json +++ b/example/deno.json @@ -2,7 +2,7 @@ "compilerOptions": { "lib": ["deno.window", "dom"] }, "imports": { - "react":"https://esm.sh/preact@10.13.2/compat", + "react":"https://esm.sh/preact@10.15.1/compat", "app": "./app.tsx" } } \ No newline at end of file diff --git a/example/deno.lock b/example/deno.lock new file mode 100644 index 0000000..b84dd19 --- /dev/null +++ b/example/deno.lock @@ -0,0 +1,11 @@ +{ + "version": "2", + "remote": { + "https://esm.sh/preact@10.13.2/compat/jsx-runtime": "cd2ac8b136b917d804161f394f0838e5b1643cd9a1f4ba7edc52e50caaa419d2", + "https://esm.sh/stable/preact@10.13.2/deno/compat.js": "3151a948abd84aa75dfc9e57733da7e1a45fff7a25de58c7b6025b923874b508", + "https://esm.sh/stable/preact@10.13.2/deno/compat/jsx-runtime.js": "e042cc9c6a59023f70840c4e6f9854fce0486241583e0e471c67d2b6cecf849f", + "https://esm.sh/stable/preact@10.13.2/deno/hooks.js": "c7a8e703bcbc6a05949f329b618c33d5d1ea5fee113ddcea44ff0f527af8556f", + "https://esm.sh/stable/preact@10.13.2/deno/jsx-runtime.js": "dd40c5bdfe7b277bf51009fb950c550dfb554b6d56a1b3a4cb9bc12bde84bcd1", + "https://esm.sh/stable/preact@10.13.2/deno/preact.mjs": "365fab897381f4f403f859c5d12939084560545567108cc90dae901bbe892578" + } +} diff --git a/local.tsx b/local.tsx index 9affb83..08d7706 100644 --- a/local.tsx +++ b/local.tsx @@ -20,7 +20,17 @@ Configure({ } } }, - + Remap: (inImports)=> + { + Object.entries(inImports).forEach(([key, value])=> + { + if(value.startsWith("./")) + { + inImports[key] = value.substring(1); + } + }); + return inImports; + }, Serve(inReq, inURL, inExt, inMap) { if(inReq.headers.get("upgrade") == "websocket") @@ -36,13 +46,8 @@ Configure({ } catch(e) { - return new Response(e); } } - else - { - return false; - } } }); diff --git a/serve.tsx b/serve.tsx index a7dbacd..4e08888 100644 --- a/serve.tsx +++ b/serve.tsx @@ -2,22 +2,28 @@ import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts"; import * as HTTP from "https://deno.land/std@0.177.0/http/server.ts"; import * as SWCW from "https://esm.sh/@swc/wasm-web@1.3.62"; -const ImportMap = {imports:{}}; +type DenoConfig = {imports:Record}; +const ImportMap:DenoConfig = {imports:{}}; const ImportMapReload =async()=> { - let confText; + let json:DenoConfig; + const path = Configuration.Proxy+"/deno.json"; try { - confText = await Deno.readTextFile(Deno.cwd()+"\\deno.json"); + const resp = await fetch(path); + json = await resp.json(); + if(!json?.imports) + { throw new Error("imports not specified in deno.json") } } catch(e) { - console.log(`No "deno.json" file found at "${Deno.cwd()}"`); + console.log(`error reading deno config "${path}" message:"${e}"`); + return; } - confText && (ImportMap.imports = Configuration.Remap(JSON.parse(confText)?.imports || {})); + ImportMap.imports = Configuration.Remap(json.imports); }; -type CustomHTTPHandler = (inReq:Request, inURL:URL, inExt:string|false, inMap:{imports:Record})=>false|Response|Promise; +type CustomHTTPHandler = (inReq:Request, inURL:URL, inExt:string|false, inMap:{imports:Record})=>void|false|Response|Promise; type CustomRemapper = (inImports:Record)=>Record; type Configuration = {Proxy:string, Allow:string, Reset:string, SWCOp:SWCW.Options, Serve:CustomHTTPHandler, Shell:CustomHTTPHandler, Remap:CustomRemapper}; type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, SWCOp?:SWCW.Options, Serve?:CustomHTTPHandler, Shell?:CustomHTTPHandler, Remap?:CustomRemapper}; @@ -26,10 +32,7 @@ let Configuration:Configuration = Proxy: `file://${Deno.cwd().replaceAll("\\", "/")}`, Allow: "*", Reset: "/clear-cache", - Serve(inReq, inURL, inExt, inMap) - { - return false; - }, + Serve(inReq, inURL, inExt, inMap){}, Remap: (inImports)=> { Object.entries(inImports).forEach(([key, value])=> @@ -39,7 +42,13 @@ let Configuration:Configuration = inImports[key] = value.substring(1); } }); - Configuration.SWCOp.jsc.transform.react.importSource = inImports["react"]; + const reactURL = inImports["react"] ?? console.log("React is not defined in imports"); + const setting = Configuration.SWCOp?.jsc?.transform?.react; + if(setting) + { + setting.importSource = reactURL; + } + console.log(inImports); return inImports; }, Shell(inReq, inURL, inExt, inMap) @@ -119,7 +128,7 @@ export const Transpile = } catch(e) { - //console.log(`Transpile.Fetch error. Key:"${inKey}" Path:"${inPath}" Error:"${e}"`); + console.log(`Transpile.Fetch error. Key:"${inKey}" Path:"${inPath}" Error:"${e}"`); return null; } } -- 2.34.1 From a9e4c0bb35ff58471dd9ba216813956fc27326d0 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Thu, 8 Jun 2023 06:37:01 -0400 Subject: [PATCH 08/25] start transpile adjacent server files --- local.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/local.tsx b/local.tsx index 08d7706..4a43f00 100644 --- a/local.tsx +++ b/local.tsx @@ -31,8 +31,18 @@ Configure({ }); return inImports; }, - Serve(inReq, inURL, inExt, inMap) + async Serve(inReq, inURL, inExt, inMap) { + if(inURL.pathname.startsWith("/hmr/")) + { + const path = import.meta.url+"/.."+inURL.pathname; + const code = await Transpile.Fetch(path, inURL.pathname, true); + if(code) + { + return new Response(code, {headers:{"content-type":"application/javascript"}}) + } + } + if(inReq.headers.get("upgrade") == "websocket") { try -- 2.34.1 From 6b8a20780987b1930a6f3b4e7dfb023ce28ee8b1 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Thu, 8 Jun 2023 16:59:56 -0400 Subject: [PATCH 09/25] make _lib_ accessable --- {hmr => _lib_}/hmr.tsx | 0 {hmr => _lib_}/react.tsx | 0 local.tsx | 18 +++--------------- serve.tsx | 16 ++++++++++++++-- 4 files changed, 17 insertions(+), 17 deletions(-) rename {hmr => _lib_}/hmr.tsx (100%) rename {hmr => _lib_}/react.tsx (100%) diff --git a/hmr/hmr.tsx b/_lib_/hmr.tsx similarity index 100% rename from hmr/hmr.tsx rename to _lib_/hmr.tsx diff --git a/hmr/react.tsx b/_lib_/react.tsx similarity index 100% rename from hmr/react.tsx rename to _lib_/react.tsx diff --git a/local.tsx b/local.tsx index 4a43f00..e29ed2f 100644 --- a/local.tsx +++ b/local.tsx @@ -31,18 +31,8 @@ Configure({ }); return inImports; }, - async Serve(inReq, inURL, inExt, inMap) + Serve(inReq, inURL, inExt, inMap) { - if(inURL.pathname.startsWith("/hmr/")) - { - const path = import.meta.url+"/.."+inURL.pathname; - const code = await Transpile.Fetch(path, inURL.pathname, true); - if(code) - { - return new Response(code, {headers:{"content-type":"application/javascript"}}) - } - } - if(inReq.headers.get("upgrade") == "websocket") { try @@ -54,9 +44,7 @@ Configure({ socket.onerror = (e) => console.log("Socket errored:", e); return response; } - catch(e) - { - } + catch(e){ /**/ } } } }); @@ -92,4 +80,4 @@ for await (const event of Deno.watchFs(Deno.cwd())) } , 1000); } -} +} \ No newline at end of file diff --git a/serve.tsx b/serve.tsx index 4e08888..53fa90d 100644 --- a/serve.tsx +++ b/serve.tsx @@ -178,8 +178,20 @@ HTTP.serve(async(req: Request)=> // transpileable files if(Transpile.Check(ext)) { - const lookup = await Transpile.Fetch(Configuration.Proxy + url.pathname, url.pathname); - return new Response(lookup, {status:lookup?200:404, headers:{...headers, "content-type":"application/javascript"}} ); + if(url.pathname.startsWith("/_lib_/")) + { + const path = import.meta.url+"/.."+url.pathname; + const code = await Transpile.Fetch(path, url.pathname, true); + if(code) + { + return new Response(code, {headers:{"content-type":"application/javascript"}}); + } + } + else + { + const lookup = await Transpile.Fetch(Configuration.Proxy + url.pathname, url.pathname); + return new Response(lookup, {status:lookup?200:404, headers:{...headers, "content-type":"application/javascript"}} ); + } } // all other static files -- 2.34.1 From 0e6a3d3efb04359493ef5a38019ab5797f72cc6d Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Thu, 8 Jun 2023 23:19:03 -0400 Subject: [PATCH 10/25] hmr roughed in (you have to clear-cache?) --- deno.json | 1 + example/app.tsx | 3 +-- local.tsx | 25 ++++++++++++++++++++++++- serve.tsx | 23 ++++++++++++----------- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/deno.json b/deno.json index 9a94b43..1f5fffd 100644 --- a/deno.json +++ b/deno.json @@ -6,6 +6,7 @@ "imports": { "react":"https://esm.sh/preact@10.15.1/compat", + "react-original":"https://esm.sh/preact@10.15.1/compat", "app": "./app.tsx" } } \ No newline at end of file diff --git a/example/app.tsx b/example/app.tsx index ed9b503..03c2ce0 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -1,5 +1,4 @@ - - +import React from "react"; export default ()=> { return

hey!

diff --git a/local.tsx b/local.tsx index e29ed2f..ced1167 100644 --- a/local.tsx +++ b/local.tsx @@ -29,10 +29,33 @@ Configure({ inImports[key] = value.substring(1); } }); + + inImports["react-original"] = inImports["react"]; + inImports["react"] = "/_lib_/react.tsx"; + console.log(inImports); return inImports; }, - Serve(inReq, inURL, inExt, inMap) + async Serve(inReq, inURL, inExt, inMap) { + if(Transpile.Check(inExt) && !inURL.searchParams.get("reload") && !inURL.pathname.startsWith("/_lib_/")) + { + const imp = await import(Directory+inURL.pathname); + const members = []; + for( const key in imp ) { members.push(key); } + return new Response(`import {FileListen} from "/_lib_/hmr.tsx"; + import * as Import from "${inURL.pathname}?reload=0"; + ${ members.map(m=>`let proxy_${m} = Import.${m}; + export { proxy_${m} as ${m} }; + `).join(" ") } + const reloadHandler = (updatedModule)=> + { + ${ members.map(m=>`proxy_${m} = updatedModule.${m};`).join("\n") } + }; + FileListen("${inURL.pathname}", reloadHandler);`, {headers:{"content-type":"application/javascript"}} + ); + } + + if(inReq.headers.get("upgrade") == "websocket") { try diff --git a/serve.tsx b/serve.tsx index 53fa90d..b50d80b 100644 --- a/serve.tsx +++ b/serve.tsx @@ -64,7 +64,8 @@ let Configuration:Configuration = `, {status:200, headers:{"content-type":"text/html"}}); @@ -165,16 +166,6 @@ HTTP.serve(async(req: Request)=> return custom; } - // custom page html - if(!ext) - { - const shell = await Configuration.Shell(req, url, ext, ImportMap); - if(shell) - { - return shell; - } - } - // transpileable files if(Transpile.Check(ext)) { @@ -194,6 +185,16 @@ HTTP.serve(async(req: Request)=> } } + // custom page html + if(!ext) + { + const shell = await Configuration.Shell(req, url, ext, ImportMap); + if(shell) + { + return shell; + } + } + // all other static files if(ext) { -- 2.34.1 From fc674f2efb3545022e74b7d70aa2de5292979ae6 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Thu, 15 Jun 2023 17:13:51 -0400 Subject: [PATCH 11/25] started --- _lib_/hmr.tsx | 41 +++++++++++++++++++++-------------------- deno.json | 7 ++++++- example/app.tsx | 10 +++++++++- example/deno.lock | 11 ----------- local.tsx | 1 + serve.tsx | 6 +++++- 6 files changed, 42 insertions(+), 34 deletions(-) delete mode 100644 example/deno.lock diff --git a/_lib_/hmr.tsx b/_lib_/hmr.tsx index 198981e..d76d7e6 100644 --- a/_lib_/hmr.tsx +++ b/_lib_/hmr.tsx @@ -1,28 +1,29 @@ - -let reloads = 0; -const listeners = new Map() as Mapvoid>>; -const socket:WebSocket = new WebSocket("ws://"+document.location.host); -socket.addEventListener('message', (event) => -{ - let handlers = listeners.get(event.data)??[]; - reloads++; - Promise.all( - handlers.map(handler=> - { - return import(event.data+"?reload="+reloads) - .then(updatedModule=>handler(updatedModule)); - }) - ).then(()=>HMR.update()); -}); -const socketTimer = setInterval(()=>{socket.send("ping")}, 1000); - +type FileHandler = (module:unknown)=>void +const FileListeners = new Map() as Map>; export const FileListen =(inPath:string, inHandler:()=>void)=> { - const members = listeners.get(inPath)??[]; + const members = FileListeners.get(inPath)??[]; members.push(inHandler); - listeners.set(inPath, members); + FileListeners.set(inPath, members); }; +const Socket:WebSocket = new WebSocket("ws://"+document.location.host); +Socket.addEventListener('message', (event:{data:string})=> +{ + const handlers = FileListeners.get(event.data)??[]; + SocketReloads++; + Promise.all( + handlers.map((handler)=> + { + return import(event.data+"?reload="+SocketReloads) + .then(updatedModule=>handler(updatedModule)); + }) + ).then(HMR.update); +}); +let SocketReloads = 0; +// heartbeat +const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); + const HMR = { reloads:0, registered: new Map() as Mapvoid>, diff --git a/deno.json b/deno.json index 1f5fffd..a542f8e 100644 --- a/deno.json +++ b/deno.json @@ -1,12 +1,17 @@ { "compilerOptions": { "lib": ["deno.window", "dom"], "jsx": "react-jsx", - "jsxImportSource": "https://esm.sh/preact@10.15.1" + "jsxImportSource": "https://esm.sh/preact@10.15.1/compat" }, "imports": { "react":"https://esm.sh/preact@10.15.1/compat", "react-original":"https://esm.sh/preact@10.15.1/compat", "app": "./app.tsx" + }, + "tasks": + { + "local": "deno run -A --no-lock ./local.tsx", + "serve": "deno run -A --no-lock ./serve.tsx" } } \ No newline at end of file diff --git a/example/app.tsx b/example/app.tsx index 03c2ce0..3814f18 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -1,5 +1,13 @@ import React from "react"; + +const CTX = React.createContext("lol"); + export default ()=> { - return

hey!

+ return +

hey!?

+ + {(value)=>} + +
} \ No newline at end of file diff --git a/example/deno.lock b/example/deno.lock deleted file mode 100644 index b84dd19..0000000 --- a/example/deno.lock +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "2", - "remote": { - "https://esm.sh/preact@10.13.2/compat/jsx-runtime": "cd2ac8b136b917d804161f394f0838e5b1643cd9a1f4ba7edc52e50caaa419d2", - "https://esm.sh/stable/preact@10.13.2/deno/compat.js": "3151a948abd84aa75dfc9e57733da7e1a45fff7a25de58c7b6025b923874b508", - "https://esm.sh/stable/preact@10.13.2/deno/compat/jsx-runtime.js": "e042cc9c6a59023f70840c4e6f9854fce0486241583e0e471c67d2b6cecf849f", - "https://esm.sh/stable/preact@10.13.2/deno/hooks.js": "c7a8e703bcbc6a05949f329b618c33d5d1ea5fee113ddcea44ff0f527af8556f", - "https://esm.sh/stable/preact@10.13.2/deno/jsx-runtime.js": "dd40c5bdfe7b277bf51009fb950c550dfb554b6d56a1b3a4cb9bc12bde84bcd1", - "https://esm.sh/stable/preact@10.13.2/deno/preact.mjs": "365fab897381f4f403f859c5d12939084560545567108cc90dae901bbe892578" - } -} diff --git a/local.tsx b/local.tsx index ced1167..d30567d 100644 --- a/local.tsx +++ b/local.tsx @@ -22,6 +22,7 @@ Configure({ }, Remap: (inImports)=> { + console.log("running remapper"); Object.entries(inImports).forEach(([key, value])=> { if(value.startsWith("./")) diff --git a/serve.tsx b/serve.tsx index b50d80b..e224a03 100644 --- a/serve.tsx +++ b/serve.tsx @@ -143,7 +143,11 @@ export const Extension =(inPath:string)=> return posDot > posSlash ? inPath.substring(posDot+1).toLowerCase() : false; }; -export const Configure =(config:ConfigurationArgs)=> Configuration = {...Configuration, ...config}; +export const Configure =(config:ConfigurationArgs)=> +{ + Configuration = {...Configuration, ...config}; + ImportMapReload(); +} await ImportMapReload(); await SWCW.default(); -- 2.34.1 From b042cf337a30db45f8e468cb6501acad6a8763d2 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Thu, 15 Jun 2023 17:50:55 -0400 Subject: [PATCH 12/25] more tweaks --- _lib_/hmr.tsx | 12 ++++++------ example/app.tsx | 2 +- example/deno.json | 5 +++++ local.tsx | 22 +++++++++++----------- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/_lib_/hmr.tsx b/_lib_/hmr.tsx index d76d7e6..d293d84 100644 --- a/_lib_/hmr.tsx +++ b/_lib_/hmr.tsx @@ -18,7 +18,7 @@ Socket.addEventListener('message', (event:{data:string})=> return import(event.data+"?reload="+SocketReloads) .then(updatedModule=>handler(updatedModule)); }) - ).then(HMR.update); + ).then(()=>HMR.update()); }); let SocketReloads = 0; // heartbeat @@ -26,19 +26,19 @@ const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); const HMR = { reloads:0, - registered: new Map() as Mapvoid>, + createdElements: new Map() as Mapvoid>, states: new Map(), statesOld: new Map(), wireframe: false, - onChange(key:string, value:()=>void):void + onChange(reactID:string, value:()=>void):void { - this.registered.set(key, value); + this.createdElements.set(reactID, value); }, update() { this.reloads++; - this.registered.forEach(handler=>handler()); - this.registered.clear(); + this.createdElements.forEach(handler=>handler()); + this.createdElements.clear(); this.statesOld = this.states; this.states = new Map(); this.echoState(); diff --git a/example/app.tsx b/example/app.tsx index 3814f18..29f3e8b 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -5,7 +5,7 @@ const CTX = React.createContext("lol"); export default ()=> { return -

hey!?

+

hey!

{(value)=>} diff --git a/example/deno.json b/example/deno.json index 8256efb..9f0718b 100644 --- a/example/deno.json +++ b/example/deno.json @@ -4,5 +4,10 @@ { "react":"https://esm.sh/preact@10.15.1/compat", "app": "./app.tsx" + }, + "tasks": + { + "local": "deno run -A --no-lock ../local.tsx", + "serve": "deno run -A --no-lock ../serve.tsx" } } \ No newline at end of file diff --git a/local.tsx b/local.tsx index d30567d..aee018c 100644 --- a/local.tsx +++ b/local.tsx @@ -43,17 +43,17 @@ Configure({ const imp = await import(Directory+inURL.pathname); const members = []; for( const key in imp ) { members.push(key); } - return new Response(`import {FileListen} from "/_lib_/hmr.tsx"; - import * as Import from "${inURL.pathname}?reload=0"; - ${ members.map(m=>`let proxy_${m} = Import.${m}; - export { proxy_${m} as ${m} }; - `).join(" ") } - const reloadHandler = (updatedModule)=> - { - ${ members.map(m=>`proxy_${m} = updatedModule.${m};`).join("\n") } - }; - FileListen("${inURL.pathname}", reloadHandler);`, {headers:{"content-type":"application/javascript"}} - ); + + const code =` +import {FileListen} from "/_lib_/hmr.tsx"; +import * as Import from "${inURL.pathname}?reload=0"; +${ members.map(m=>`let proxy_${m} = Import.${m}; export { proxy_${m} as ${m} };`).join("\n") } +FileListen("${inURL.pathname}", (updatedModule)=> +{ + ${ members.map(m=>`proxy_${m} = updatedModule.${m};`).join("\n") } +});` + + return new Response(code, {headers:{"content-type":"application/javascript"}}); } -- 2.34.1 From 50aaaf136e1f0ffad0e1eb4bfa86e069728270b2 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Thu, 15 Jun 2023 23:08:24 -0400 Subject: [PATCH 13/25] shuffle --- _lib_/hmr.tsx | 22 +++++----------------- _lib_/react.tsx | 23 +++++++++++++++++++++-- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/_lib_/hmr.tsx b/_lib_/hmr.tsx index d293d84..274b968 100644 --- a/_lib_/hmr.tsx +++ b/_lib_/hmr.tsx @@ -1,3 +1,5 @@ +import { type StateCapture } from "./react.tsx"; + type FileHandler = (module:unknown)=>void const FileListeners = new Map() as Map>; export const FileListen =(inPath:string, inHandler:()=>void)=> @@ -27,8 +29,8 @@ const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); const HMR = { reloads:0, createdElements: new Map() as Mapvoid>, - states: new Map(), - statesOld: new Map(), + states: new Map() as Map, + statesOld: new Map() as Map, wireframe: false, onChange(reactID:string, value:()=>void):void { @@ -60,18 +62,4 @@ const HMR = { } }; -export {HMR}; - -export const MapAt =(inMap, inIndex)=> -{ - let index = 0; - for(const kvp of inMap) - { - if(index == inIndex) - { - return kvp; - } - index++; - } - return false; -}; \ No newline at end of file +export {HMR}; \ No newline at end of file diff --git a/_lib_/react.tsx b/_lib_/react.tsx index 3b013cd..042307d 100644 --- a/_lib_/react.tsx +++ b/_lib_/react.tsx @@ -1,5 +1,24 @@ import * as ReactParts from "react-original"; -import { HMR, MapAt } from "./hmr.tsx"; +import { HMR } from "./hmr.tsx"; + +export type StateType = boolean|number|string|Record +export type StateCapture = {state:StateType, set:ReactParts.StateUpdater, reload:number}; + +const pluck =(m:Map)=> m.keys() + +const MapAt =(inMap:Map, inIndex:number)=> +{ + let index = 0; + for(const kvp of inMap) + { + if(index == inIndex) + { + return kvp; + } + index++; + } + return false; +}; const H = ReactParts.createElement; @@ -29,7 +48,7 @@ const ProxyCreate =(...args)=> return typeof args[0] != "string" ? H(ProxyElement, {__args:args, ...args[1]}) : H(...args); }; -const ProxyState =(arg)=> +const ProxyState =(arg:StateType)=> { const id = ReactParts.useId(); const trueArg = arg; -- 2.34.1 From 73cefa9a0edc6d0986486457ee37e509da68c2bb Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 16 Jun 2023 14:59:27 -0400 Subject: [PATCH 14/25] misc --- _lib_/hmr.tsx | 22 +++------------------- _lib_/react.tsx | 36 ++++++++++++++++++++---------------- example/app.tsx | 40 +++++++++++++++++++++++++++++++++------- 3 files changed, 56 insertions(+), 42 deletions(-) diff --git a/_lib_/hmr.tsx b/_lib_/hmr.tsx index 274b968..2089a27 100644 --- a/_lib_/hmr.tsx +++ b/_lib_/hmr.tsx @@ -29,7 +29,7 @@ const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); const HMR = { reloads:0, createdElements: new Map() as Mapvoid>, - states: new Map() as Map, + statesNew: new Map() as Map, statesOld: new Map() as Map, wireframe: false, onChange(reactID:string, value:()=>void):void @@ -41,24 +41,8 @@ const HMR = { this.reloads++; this.createdElements.forEach(handler=>handler()); this.createdElements.clear(); - this.statesOld = this.states; - this.states = new Map(); - this.echoState(); - }, - echoState() - { - let output = []; - for(const[key, val] of HMR.statesOld) - { - output[key] = val.state+"--"+val.reload; - } - console.log(output); - output = []; - for(const[key, val] of HMR.states) - { - output[key] = val.state+"--"+val.reload; - } - console.log(output); + this.statesOld = this.statesNew; + this.statesNew = new Map(); } }; diff --git a/_lib_/react.tsx b/_lib_/react.tsx index 042307d..c822191 100644 --- a/_lib_/react.tsx +++ b/_lib_/react.tsx @@ -3,9 +3,9 @@ import { HMR } from "./hmr.tsx"; export type StateType = boolean|number|string|Record export type StateCapture = {state:StateType, set:ReactParts.StateUpdater, reload:number}; +type FuncArgs = [element:keyof ReactParts.JSX.IntrinsicElements, props:Record, children:ReactParts.JSX.Element[]]; -const pluck =(m:Map)=> m.keys() - +const H = ReactParts.createElement; const MapAt =(inMap:Map, inIndex:number)=> { let index = 0; @@ -20,9 +20,7 @@ const MapAt =(inMap:Map, inIndex:number)=> return false; }; -const H = ReactParts.createElement; - -const ProxyElement = (props)=> +const ProxyElement = (props:{__args:FuncArgs})=> { const id = ReactParts.useId(); const [stateGet, stateSet] = ReactParts.useState(0); @@ -43,7 +41,7 @@ const ProxyElement = (props)=> } }; -const ProxyCreate =(...args)=> +const ProxyCreate =(...args:FuncArgs)=> { return typeof args[0] != "string" ? H(ProxyElement, {__args:args, ...args[1]}) : H(...args); }; @@ -54,7 +52,7 @@ const ProxyState =(arg:StateType)=> const trueArg = arg; // does statesOld have an entry for this state? use that instead of the passed arg - const check = MapAt(HMR.statesOld, HMR.states.size); + const check = MapAt(HMR.statesOld, HMR.statesNew.size); if(check) { arg = check[1].state; @@ -68,22 +66,28 @@ const ProxyState =(arg:StateType)=> if(HMR.reloads == lastKnowReloads) { // this is a switch/ui change, not a HMR reload change - const oldState = MapAt(HMR.statesOld, HMR.states.size-1); - HMR.statesOld.set(oldState[0], {...oldState[1], state:trueArg}); + const oldState = MapAt(HMR.statesOld, HMR.statesNew.size-1); + oldState && HMR.statesOld.set(oldState[0], {...oldState[1], state:trueArg}); + + console.log("check: ui-invoked") } - HMR.states.delete(id); + else + { + console.log("check: hmr-invoked") + } + HMR.statesNew.delete(id); } }, []); - if(!HMR.states.has(id)) + if(!HMR.statesNew.has(id)) { - HMR.states.set(id, {state:arg, set:stateSet, reload:HMR.reloads}); + HMR.statesNew.set(id, {state:arg, set:stateSet, reload:HMR.reloads}); } - function proxySetter (arg) + function proxySetter (arg:StateType) { //console.log("state spy update", id, arg); - HMR.states.set(id, {state:arg, set:stateSet, reload:HMR.reloads}); + HMR.statesNew.set(id, {state:arg, set:stateSet, reload:HMR.reloads}); return stateSet(arg); } return [stateGet, proxySetter]; @@ -91,6 +95,6 @@ const ProxyState =(arg:StateType)=> }; export * from "react-original"; -export { ProxyCreate as createElement, ProxyState as useState }; +export {ProxyCreate as createElement, ProxyState as useState }; export const isProxy = true; -export default {...ReactParts.default, createElement:ProxyCreate, useState:ProxyState, isProxy:true}; \ No newline at end of file +export default {...ReactParts, createElement:ProxyCreate, useState:ProxyState, isProxy:true}; \ No newline at end of file diff --git a/example/app.tsx b/example/app.tsx index 29f3e8b..bf24835 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -1,13 +1,39 @@ +import { VNode } from "https://esm.sh/v118/preact@10.15.1/src/index.js"; import React from "react"; -const CTX = React.createContext("lol"); +const CTXString = React.createContext("lol"); + +const Butt =(props:{label:string})=> +{ + const [countGet, countSet] = React.useState(3); + return ; +}; + +type StateBinding = [get:T, set:React.StateUpdater]; +const CTXState = React.createContext(null) as React.Context|null>; +const Outer =(props:{children:VNode})=> +{ + const binding = React.useState("lol?"); + return + {props.children} + +}; +const Inner =()=> +{ + const binding = React.useContext(CTXState); + return +}; + export default ()=> { - return -

hey!

- - {(value)=>} - -
+ return +

hey???

+ + + + + + +
} \ No newline at end of file -- 2.34.1 From 24ba4e8f5b97f7c0414f91dd19f5b86b3cb3ae14 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Sat, 17 Jun 2023 14:48:39 -0400 Subject: [PATCH 15/25] moe cleanup --- _lib_/hmr.tsx | 37 +++++++++++++++++-------------------- _lib_/react.tsx | 33 ++++++++++++++++++--------------- example/app.tsx | 2 +- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/_lib_/hmr.tsx b/_lib_/hmr.tsx index 2089a27..7bbe86b 100644 --- a/_lib_/hmr.tsx +++ b/_lib_/hmr.tsx @@ -1,7 +1,7 @@ import { type StateCapture } from "./react.tsx"; -type FileHandler = (module:unknown)=>void -const FileListeners = new Map() as Map>; + +const FileListeners = new Map() as Mapvoid>>; export const FileListen =(inPath:string, inHandler:()=>void)=> { const members = FileListeners.get(inPath)??[]; @@ -10,37 +10,34 @@ export const FileListen =(inPath:string, inHandler:()=>void)=> }; const Socket:WebSocket = new WebSocket("ws://"+document.location.host); -Socket.addEventListener('message', (event:{data:string})=> +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 + const reImport = await import(event.data+"?reload="+HMR.reloads); const handlers = FileListeners.get(event.data)??[]; - SocketReloads++; - Promise.all( - handlers.map((handler)=> - { - return import(event.data+"?reload="+SocketReloads) - .then(updatedModule=>handler(updatedModule)); - }) - ).then(()=>HMR.update()); + handlers.forEach(handler=>handler(reImport)); + HMR.update(); }); -let SocketReloads = 0; -// heartbeat +Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HRM socket lost")}) const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); -const HMR = { - reloads:0, - createdElements: new Map() as Mapvoid>, +const HMR = +{ + reloads:1, + RegisteredComponents: new Map() as Mapvoid>, statesNew: new Map() as Map, statesOld: new Map() as Map, wireframe: false, - onChange(reactID:string, value:()=>void):void + RegisterComponent(reactID:string, value:()=>void):void { - this.createdElements.set(reactID, value); + this.RegisteredComponents.set(reactID, value); }, update() { this.reloads++; - this.createdElements.forEach(handler=>handler()); - this.createdElements.clear(); + this.RegisteredComponents.forEach(handler=>handler()); + this.RegisteredComponents.clear(); this.statesOld = this.statesNew; this.statesNew = new Map(); } diff --git a/_lib_/react.tsx b/_lib_/react.tsx index c822191..cc6ca23 100644 --- a/_lib_/react.tsx +++ b/_lib_/react.tsx @@ -20,11 +20,24 @@ const MapAt =(inMap:Map, inIndex:number)=> return false; }; + +const ProxyCreate =(...args:FuncArgs)=> +{ + if(typeof args[0] == "string") + { + return H(...args) + } + else + { + return H(ProxyElement, {__args:args, ...args[1]}); + } +}; + const ProxyElement = (props:{__args:FuncArgs})=> { - const id = ReactParts.useId(); const [stateGet, stateSet] = ReactParts.useState(0); - ReactParts.useEffect(()=>HMR.onChange(id, ()=>stateSet(stateGet+1))); + const id = ReactParts.useId(); + HMR.RegisterComponent(id, ()=>stateSet(stateGet+1)); const child = H(...props.__args); @@ -37,37 +50,27 @@ const ProxyElement = (props:{__args:FuncArgs})=> } else { - return child; + return child; } }; -const ProxyCreate =(...args:FuncArgs)=> -{ - return typeof args[0] != "string" ? H(ProxyElement, {__args:args, ...args[1]}) : H(...args); -}; const ProxyState =(arg:StateType)=> { const id = ReactParts.useId(); - const trueArg = arg; // does statesOld have an entry for this state? use that instead of the passed arg const check = MapAt(HMR.statesOld, HMR.statesNew.size); - if(check) - { - arg = check[1].state; - console.info(`BOOTING with ${arg}`); - } const lastKnowReloads = HMR.reloads; - const [stateGet, stateSet] = ReactParts.useState(arg); + const [stateGet, stateSet] = ReactParts.useState(check ? check[1].state : arg); ReactParts.useEffect(()=>{ return ()=>{ if(HMR.reloads == lastKnowReloads) { // this is a switch/ui change, not a HMR reload change const oldState = MapAt(HMR.statesOld, HMR.statesNew.size-1); - oldState && HMR.statesOld.set(oldState[0], {...oldState[1], state:trueArg}); + oldState && HMR.statesOld.set(oldState[0], {...oldState[1], state:arg}); console.log("check: ui-invoked") } diff --git a/example/app.tsx b/example/app.tsx index bf24835..43850ac 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -28,7 +28,7 @@ const Inner =()=> export default ()=> { return -

hey???

+

Title

-- 2.34.1 From a781b3300c7d6d3b12c88d75151d51c2cddfe091 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Sun, 18 Jun 2023 07:36:55 -0400 Subject: [PATCH 16/25] arg new/old --- _lib_/hmr.tsx | 2 +- _lib_/react.tsx | 25 ++++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/_lib_/hmr.tsx b/_lib_/hmr.tsx index 7bbe86b..854d503 100644 --- a/_lib_/hmr.tsx +++ b/_lib_/hmr.tsx @@ -19,7 +19,7 @@ Socket.addEventListener('message', async(event:{data:string})=> handlers.forEach(handler=>handler(reImport)); HMR.update(); }); -Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HRM socket lost")}) +Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")}) const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); const HMR = diff --git a/_lib_/react.tsx b/_lib_/react.tsx index cc6ca23..4a2fffc 100644 --- a/_lib_/react.tsx +++ b/_lib_/react.tsx @@ -6,7 +6,7 @@ export type StateCapture = {state:StateType, set:ReactParts.StateUpdater, children:ReactParts.JSX.Element[]]; const H = ReactParts.createElement; -const MapAt =(inMap:Map, inIndex:number)=> +const MapIndex =(inMap:Map, inIndex:number)=> { let index = 0; for(const kvp of inMap) @@ -58,18 +58,24 @@ const ProxyElement = (props:{__args:FuncArgs})=> const ProxyState =(arg:StateType)=> { const id = ReactParts.useId(); - + //const argOriginal = arg; // does statesOld have an entry for this state? use that instead of the passed arg - const check = MapAt(HMR.statesOld, HMR.statesNew.size); + const check = MapIndex(HMR.statesOld, HMR.statesNew.size); + const argOld = check ? check[1].state : arg; const lastKnowReloads = HMR.reloads; - const [stateGet, stateSet] = ReactParts.useState(check ? check[1].state : arg); + const [stateGet, stateSet] = ReactParts.useState(argOld); ReactParts.useEffect(()=>{ + + + + /* + i have no idea what this does return ()=>{ if(HMR.reloads == lastKnowReloads) { // this is a switch/ui change, not a HMR reload change - const oldState = MapAt(HMR.statesOld, HMR.statesNew.size-1); + const oldState = MapIndex(HMR.statesOld, HMR.statesNew.size-1); oldState && HMR.statesOld.set(oldState[0], {...oldState[1], state:arg}); console.log("check: ui-invoked") @@ -80,18 +86,19 @@ const ProxyState =(arg:StateType)=> } HMR.statesNew.delete(id); } + */ }, []); if(!HMR.statesNew.has(id)) { - HMR.statesNew.set(id, {state:arg, set:stateSet, reload:HMR.reloads}); + HMR.statesNew.set(id, {state:argOld, set:stateSet, reload:HMR.reloads}); } - function proxySetter (arg:StateType) + function proxySetter (inArg:StateType) { //console.log("state spy update", id, arg); - HMR.statesNew.set(id, {state:arg, set:stateSet, reload:HMR.reloads}); - return stateSet(arg); + HMR.statesNew.set(id, {state:inArg, set:stateSet, reload:HMR.reloads}); + return stateSet(inArg); } return [stateGet, proxySetter]; -- 2.34.1 From 4dbc5ab1d5700313ed302aa2665987a8c94efcf2 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Sun, 18 Jun 2023 14:37:13 -0400 Subject: [PATCH 17/25] documentation/clarification --- _lib_/hmr.tsx | 34 ++++++++++++++++++++++++--- _lib_/react.tsx | 61 ++++++++++++++++++------------------------------- example/app.tsx | 13 ++++------- 3 files changed, 58 insertions(+), 50 deletions(-) diff --git a/_lib_/hmr.tsx b/_lib_/hmr.tsx index 854d503..3abd95f 100644 --- a/_lib_/hmr.tsx +++ b/_lib_/hmr.tsx @@ -14,14 +14,42 @@ 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 - const reImport = await import(event.data+"?reload="+HMR.reloads); - const handlers = FileListeners.get(event.data)??[]; - handlers.forEach(handler=>handler(reImport)); + const reImport = await import(event.data+"?reload="+Math.random()); + FileListeners.get(event.data)?.forEach(reExport=>reExport(reImport)); HMR.update(); }); Socket.addEventListener("error", ()=>{clearInterval(SocketTimer); console.log("HMR socket lost")}) const SocketTimer = setInterval(()=>{Socket.send("ping")}, 5000); + +/* + +Each custom component is secretly modified to have an extra state and id. +When there is an HMR update, this state is changed, forcing it to re-render. + +Each *user-created* React.useState is secretly modified and accompanied by an ID. +Every time its state is set, the HMR.statesNew map for this ID is set to contain the new state and updater. +When a component is removed, any of it's states in HMR.statesNew are also removed. +(HMR.statesNew is the "running total" of all states currently at play). + +--- + +When a state is interacted with: +- statesNew for this id is set +- the internal state is also set in the traditional way + +When there is an HMR update: +- All custom components are re-rendered... + for each useState(value) call that then happens in the re-render: + - if there is a "statesOld" value for this state, use that and ignore the passed value, otherwise use the passed value + - if this state has not been interacted with since the last reload (statesNew is empty at this id), set statesNew with whatever is in statesOld +- statesNew is moved into *statesOld* +- statesNew is cleared. + + +*/ + + const HMR = { reloads:1, diff --git a/_lib_/react.tsx b/_lib_/react.tsx index 4a2fffc..c899811 100644 --- a/_lib_/react.tsx +++ b/_lib_/react.tsx @@ -5,6 +5,7 @@ export type StateType = boolean|number|string|Record export type StateCapture = {state:StateType, set:ReactParts.StateUpdater, reload:number}; type FuncArgs = [element:keyof ReactParts.JSX.IntrinsicElements, props:Record, children:ReactParts.JSX.Element[]]; + const H = ReactParts.createElement; const MapIndex =(inMap:Map, inIndex:number)=> { @@ -20,18 +21,7 @@ const MapIndex =(inMap:Map, inIndex:number)=> return false; }; - -const ProxyCreate =(...args:FuncArgs)=> -{ - if(typeof args[0] == "string") - { - return H(...args) - } - else - { - return H(ProxyElement, {__args:args, ...args[1]}); - } -}; +const ProxyCreate =(...args:FuncArgs)=> (typeof args[0] == "string") ? H(...args) : H(ProxyElement, {__args:args, ...args[1]}); const ProxyElement = (props:{__args:FuncArgs})=> { @@ -54,51 +44,44 @@ const ProxyElement = (props:{__args:FuncArgs})=> } }; - -const ProxyState =(arg:StateType)=> +const ProxyState =(argNew:StateType)=> { - const id = ReactParts.useId(); - //const argOriginal = arg; // does statesOld have an entry for this state? use that instead of the passed arg const check = MapIndex(HMR.statesOld, HMR.statesNew.size); - const argOld = check ? check[1].state : arg; + const argOld = check ? check[1].state : argNew; + + const id = ReactParts.useId(); + const [stateGet, stateSet] = ReactParts.useState(argOld); + + // state updates due to clicks, interactivity, etc. since the last reload may already be in statesNew for this slot. + // DONT overwrite it. + if(!HMR.statesNew.get(id)) + { + HMR.statesNew.set(id, {state:stateGet, set:stateSet, reload:HMR.reloads}); + } const lastKnowReloads = HMR.reloads; - const [stateGet, stateSet] = ReactParts.useState(argOld); ReactParts.useEffect(()=>{ - - - - /* - i have no idea what this does return ()=>{ - if(HMR.reloads == lastKnowReloads) + if(HMR.reloads == lastKnowReloads)/*i have no idea what this does. this may have to be re-introduced when routing is added*/ { // this is a switch/ui change, not a HMR reload change const oldState = MapIndex(HMR.statesOld, HMR.statesNew.size-1); - oldState && HMR.statesOld.set(oldState[0], {...oldState[1], state:arg}); - + oldState && HMR.statesOld.set(oldState[0], {...oldState[1], state:argNew}); console.log("check: ui-invoked") } - else - { - console.log("check: hmr-invoked") - } + HMR.statesNew.delete(id); } - */ }, []); - if(!HMR.statesNew.has(id)) - { - HMR.statesNew.set(id, {state:argOld, set:stateSet, reload:HMR.reloads}); - } - + + // do we need to account for the function set? function proxySetter (inArg:StateType) { - //console.log("state spy update", id, arg); - HMR.statesNew.set(id, {state:inArg, set:stateSet, reload:HMR.reloads}); - return stateSet(inArg); + const stateUser = {state:inArg, set:stateSet, reload:HMR.reloads}; + HMR.statesNew.set(id, stateUser); + stateSet(inArg); } return [stateGet, proxySetter]; diff --git a/example/app.tsx b/example/app.tsx index 43850ac..f86ad81 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -3,12 +3,6 @@ import React from "react"; const CTXString = React.createContext("lol"); -const Butt =(props:{label:string})=> -{ - const [countGet, countSet] = React.useState(3); - return ; -}; - type StateBinding = [get:T, set:React.StateUpdater]; const CTXState = React.createContext(null) as React.Context|null>; const Outer =(props:{children:VNode})=> @@ -21,14 +15,17 @@ const Outer =(props:{children:VNode})=> const Inner =()=> { const binding = React.useContext(CTXState); - return + return }; export default ()=> { return -

Title

+
+

Title!

+

subtitle

+
-- 2.34.1 From b575ac39d430b7b95b866fe777e098c590d24f27 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Mon, 19 Jun 2023 09:20:16 -0400 Subject: [PATCH 18/25] fix for functional states --- _lib_/react.tsx | 25 ++++++++++++++++++++----- example/app.tsx | 8 ++++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/_lib_/react.tsx b/_lib_/react.tsx index c899811..9232813 100644 --- a/_lib_/react.tsx +++ b/_lib_/react.tsx @@ -77,11 +77,26 @@ const ProxyState =(argNew:StateType)=> // do we need to account for the function set? - function proxySetter (inArg:StateType) - { - const stateUser = {state:inArg, set:stateSet, reload:HMR.reloads}; - HMR.statesNew.set(id, stateUser); - stateSet(inArg); + function proxySetter ( inArg:StateType|((old:StateType)=>StateType) ) + { + const stateUser = {state:inArg as StateType, set:stateSet, reload:HMR.reloads}; + if(typeof inArg == "function") + { + //const passedFunction = inArg; + stateSet((oldState:StateType)=> + { + console.log("function setter intercepted"); + const output = inArg(oldState); + stateUser.state = output; + HMR.statesNew.set(id, stateUser); + return output; + }); + } + else + { + HMR.statesNew.set(id, stateUser); + stateSet(inArg); + } } return [stateGet, proxySetter]; diff --git a/example/app.tsx b/example/app.tsx index f86ad81..0bfecb5 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -4,18 +4,18 @@ import React from "react"; const CTXString = React.createContext("lol"); type StateBinding = [get:T, set:React.StateUpdater]; -const CTXState = React.createContext(null) as React.Context|null>; +const CTXState = React.createContext(null) as React.Context|null>; const Outer =(props:{children:VNode})=> { - const binding = React.useState("lol?"); + const binding = React.useState(11); return {props.children} }; const Inner =()=> { - const binding = React.useContext(CTXState); - return + const [stateGet, stateSet] = React.useContext(CTXState) || ["default", ()=>{}]; + return }; -- 2.34.1 From 68503424e6fe9d1a1d89a4424ba567e11ab1d42a Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Mon, 19 Jun 2023 12:17:40 -0400 Subject: [PATCH 19/25] useReducer proxy started! --- _lib_/react.tsx | 32 ++++++++++++++++++++++++++++---- example/app.tsx | 16 +++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/_lib_/react.tsx b/_lib_/react.tsx index 9232813..b8c9f8f 100644 --- a/_lib_/react.tsx +++ b/_lib_/react.tsx @@ -85,7 +85,6 @@ const ProxyState =(argNew:StateType)=> //const passedFunction = inArg; stateSet((oldState:StateType)=> { - console.log("function setter intercepted"); const output = inArg(oldState); stateUser.state = output; HMR.statesNew.set(id, stateUser); @@ -102,7 +101,32 @@ const ProxyState =(argNew:StateType)=> }; +type Storelike = Record +const ProxyReducer =(inReducer:(inState:Storelike, inAction:string)=>Storelike, inState:Storelike)=> +{ + const check = MapIndex(HMR.statesOld, HMR.statesNew.size); + const argOld = check ? check[1].state : inState; + + const intercept =(inInterceptState:Storelike, inInterceptAction:string)=> + { + const capture = inReducer(inInterceptState, inInterceptAction); + const stateUser = {state:capture, set:()=>{}, reload:HMR.reloads}; + HMR.statesNew.set(id, stateUser); + console.log("interepted reducer", stateUser); + return capture; + }; + + const id = ReactParts.useId(); + const [state, dispatch] = ReactParts.useReducer(intercept, argOld as Storelike); + + if(!HMR.statesNew.get(id)) + { + HMR.statesNew.set(id, {state:state, set:()=>{}, reload:HMR.reloads}); + } + + return [state, dispatch]; +}; + export * from "react-original"; -export {ProxyCreate as createElement, ProxyState as useState }; -export const isProxy = true; -export default {...ReactParts, createElement:ProxyCreate, useState:ProxyState, isProxy:true}; \ No newline at end of file +export {ProxyCreate as createElement, ProxyState as useState, ProxyReducer as useReducer }; +export default {...ReactParts, createElement:ProxyCreate, useState:ProxyState, useReducer:ProxyReducer}; \ No newline at end of file diff --git a/example/app.tsx b/example/app.tsx index 0bfecb5..d0029d4 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -1,11 +1,10 @@ -import { VNode } from "https://esm.sh/v118/preact@10.15.1/src/index.js"; import React from "react"; const CTXString = React.createContext("lol"); type StateBinding = [get:T, set:React.StateUpdater]; const CTXState = React.createContext(null) as React.Context|null>; -const Outer =(props:{children:VNode})=> +const Outer =(props:{children:Preact.VNode})=> { const binding = React.useState(11); return @@ -19,12 +18,23 @@ const Inner =()=> }; +type Store = {name:string, age:number} +const reducer =(inState:Store, inAction:number)=> +{ + return {...inState, age:inState.age+inAction}; +} + export default ()=> { + + const [Store, Dispatch] = React.useReducer(reducer, {name:"seth", age:24} as Store) return

Title!

-

subtitle

+

subtitle!

+

+ +

-- 2.34.1 From 19cc54dcea9c776f228565a58e52c805e9090012 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Mon, 19 Jun 2023 13:31:22 -0400 Subject: [PATCH 20/25] add support for init func arg --- _lib_/react.tsx | 4 ++-- example/app.tsx | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/_lib_/react.tsx b/_lib_/react.tsx index b8c9f8f..11bb7fe 100644 --- a/_lib_/react.tsx +++ b/_lib_/react.tsx @@ -102,10 +102,10 @@ const ProxyState =(argNew:StateType)=> }; type Storelike = Record -const ProxyReducer =(inReducer:(inState:Storelike, inAction:string)=>Storelike, inState:Storelike)=> +const ProxyReducer =(inReducer:(inState:Storelike, inAction:string)=>Storelike, inState:Storelike, inInit?:(inState:Storelike)=>Storelike)=> { const check = MapIndex(HMR.statesOld, HMR.statesNew.size); - const argOld = check ? check[1].state : inState; + const argOld = check ? check[1].state : (inInit ? inInit(inState) : inState); const intercept =(inInterceptState:Storelike, inInterceptAction:string)=> { diff --git a/example/app.tsx b/example/app.tsx index d0029d4..3d5d8ba 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -24,10 +24,16 @@ const reducer =(inState:Store, inAction:number)=> return {...inState, age:inState.age+inAction}; } +const builder =(inState:Store):Store=> +{ + inState.age = 100; + return inState; +} + export default ()=> { - const [Store, Dispatch] = React.useReducer(reducer, {name:"seth", age:24} as Store) + const [Store, Dispatch] = React.useReducer(reducer, {name:"seth", age:24} as Store, builder) return

Title!

-- 2.34.1 From df16163e4b6ba2d2f94904e22145dec725df3a11 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Tue, 20 Jun 2023 06:51:38 -0400 Subject: [PATCH 21/25] mount started --- _lib_/mount.tsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 _lib_/mount.tsx diff --git a/_lib_/mount.tsx b/_lib_/mount.tsx new file mode 100644 index 0000000..da350c5 --- /dev/null +++ b/_lib_/mount.tsx @@ -0,0 +1,27 @@ +import * as TW from "https://esm.sh/@twind/core@1.0.1"; +import TWPreTail from "https://esm.sh/@twind/preset-tailwind@1.0.1"; +import TWPreAuto from "https://esm.sh/@twind/preset-autoprefix@1.0.1"; + +const Configure = { + theme: {}, + presets:[TWPreTail(), TWPreAuto()] +} as TW.TwindUserConfig; + +export default (inElement:HTMLElement, inConfig?:TW.TwindUserConfig)=> +{ + const ShadowDOM = inElement.attachShadow({ mode: "open" }); + const ShadowDiv = document.createElement("div"); + const ShadowCSS = document.createElement("style"); + ShadowDOM.append(ShadowCSS); + ShadowDOM.append(ShadowDiv); + + const merge = inConfig ? {...Configure, ...inConfig} : Configure; + TW.observe(TW.twind(merge, TW.cssom(ShadowCSS)), ShadowDiv); + return ShadowDiv; +} + + +import * as App from "app"; +import React from "react"; +const Wrapper =()=> React.createElement(App.default, null, null); +React.render(React.createElement(Wrapper, null, null), document.querySelector("#app")); -- 2.34.1 From ac280aa28b03205d30e563166933994d445d651e Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Tue, 20 Jun 2023 09:50:14 -0400 Subject: [PATCH 22/25] mount added --- _lib_/mount.tsx | 46 +++++++++++++++++++++++++++++++++++++--------- deno.json | 2 +- deno.lock | 23 +++++++++++++++++++++++ example/app.tsx | 4 ++-- example/deno.json | 2 +- serve.tsx | 8 +++----- 6 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 deno.lock diff --git a/_lib_/mount.tsx b/_lib_/mount.tsx index da350c5..3eaeac1 100644 --- a/_lib_/mount.tsx +++ b/_lib_/mount.tsx @@ -1,27 +1,55 @@ +import React from "react"; import * as TW from "https://esm.sh/@twind/core@1.0.1"; import TWPreTail from "https://esm.sh/@twind/preset-tailwind@1.0.1"; import TWPreAuto from "https://esm.sh/@twind/preset-autoprefix@1.0.1"; +import react from "./react.tsx"; -const Configure = { +const Configure = +{ theme: {}, presets:[TWPreTail(), TWPreAuto()] } as TW.TwindUserConfig; -export default (inElement:HTMLElement, inConfig?:TW.TwindUserConfig)=> +export const Shadow =(inElement:HTMLElement, inConfig?:TW.TwindUserConfig)=> { + /* + const merge = inConfig ? {...Configure, ...inConfig} : Configure; + const ShadowDOM = inElement.attachShadow({ mode: "open" }); const ShadowDiv = document.createElement("div"); const ShadowCSS = document.createElement("style"); + ShadowDOM.append(ShadowCSS); ShadowDOM.append(ShadowDiv); - - const merge = inConfig ? {...Configure, ...inConfig} : Configure; TW.observe(TW.twind(merge, TW.cssom(ShadowCSS)), ShadowDiv); return ShadowDiv; -} + */ + return inElement; -import * as App from "app"; -import React from "react"; -const Wrapper =()=> React.createElement(App.default, null, null); -React.render(React.createElement(Wrapper, null, null), document.querySelector("#app")); + +}; +export const Render =async(inElement:HTMLElement, inApp:()=>React.JSX.Element)=> +{ + const wrapped = React.createElement(()=> React.createElement(inApp, null), null); + + if(React.render) + { + React.render(wrapped, inElement); + return ()=>React.unmountComponentAtNode(inElement); + } + else + { + const reactDom = await import(`https://esm.sh/react-dom@${React.version}/client`); + const root = reactDom.createRoot(inElement); + root.render(wrapped); + return root.unmount; + } +}; + +export default async(inSelector:string, inModulePath:string, inMemberApp="default", inMemberCSS="CSS"):Promise<()=>void|false>=> +{ + const members = await import(inModulePath); + const element = document.querySelector(inSelector); + return element ? await Render(Shadow(element as HTMLElement, members.CSS), members.default) : false; +}; \ No newline at end of file diff --git a/deno.json b/deno.json index a542f8e..8350e18 100644 --- a/deno.json +++ b/deno.json @@ -7,7 +7,7 @@ { "react":"https://esm.sh/preact@10.15.1/compat", "react-original":"https://esm.sh/preact@10.15.1/compat", - "app": "./app.tsx" + "@app": "./app.tsx" }, "tasks": { diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..6c863c0 --- /dev/null +++ b/deno.lock @@ -0,0 +1,23 @@ +{ + "version": "2", + "remote": { + "https://esm.sh/preact@10.15.1": "2b79349676a4942fbcf835c4efa909791c2f0aeca195225bf22bac9866e94b4e", + "https://esm.sh/preact@10.15.1/compat": "07273e22b1c335b8acc9f33c5e78165319c59bd8e2d0f3e5a2b4e028329424d9", + "https://esm.sh/react@18.2.0": "742d8246041966ba1137ec8c60888c35882a9d2478bce63583875f86c1e3687c", + "https://esm.sh/stable/preact@10.15.1/denonext/compat.js": "bad6b5b4d4fdfa5975b7a8d30410bd6877247f058e4952799fab39f66a94b8cf", + "https://esm.sh/stable/preact@10.15.1/denonext/hooks.js": "5c989ad368cf4f2cb3a5d7d1801843d9348c599fe3e7731d04728f7b845d724e", + "https://esm.sh/stable/preact@10.15.1/denonext/preact.mjs": "30710ac1d5ff3711ae0c04eddbeb706f34f82d97489f61aaf09897bc75d2a628", + "https://esm.sh/stable/react@18.2.0/deno/react.mjs": "a5a73ee24acca4744ee22c51d9357f31968d1f684ce253bde222b4e26d09f49f", + "https://esm.sh/v118/@types/prop-types@15.7.5/index.d.ts": "6a386ff939f180ae8ef064699d8b7b6e62bc2731a62d7fbf5e02589383838dea", + "https://esm.sh/v118/@types/react@18.2.0/global.d.ts": "549df62b64a71004aee17685b445a8289013daf96246ce4d9b087d13d7a27a61", + "https://esm.sh/v118/@types/react@18.2.0/index.d.ts": "b091747b1f503f434d3cac4217a13858baba87b421a7054ffdfd797da7737678", + "https://esm.sh/v118/@types/scheduler@0.16.3/tracing.d.ts": "f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5", + "https://esm.sh/v118/csstype@3.1.2/index.d.ts": "4c68749a564a6facdf675416d75789ee5a557afda8960e0803cf6711fa569288", + "https://esm.sh/v118/preact@10.15.1/compat/src/index.d.ts": "9ec63be9612a10ff72fd4183179cde7d551ce43b3a0c2f549d8f788910d8263d", + "https://esm.sh/v118/preact@10.15.1/compat/src/suspense-list.d.ts": "b8e274324392157ce476ef3a48ae215c9f7003b08525d140645f19eab20d1948", + "https://esm.sh/v118/preact@10.15.1/compat/src/suspense.d.ts": "81f5266e0977a94347505d11b8103024211f2b4f3b2eb2aa276a10d8fd169e65", + "https://esm.sh/v118/preact@10.15.1/hooks/src/index.d.ts": "933eab6436614f8cd8e9b7c9b8bd54c6f3f14c3f065ba42c8c87a42d93de6595", + "https://esm.sh/v118/preact@10.15.1/src/index.d.ts": "fa83186a4b6caca36d52ca2d49b481c3ca5460988d4a8388d44dadc28987fb27", + "https://esm.sh/v118/preact@10.15.1/src/jsx.d.ts": "a6e4b7e4af3b959f8cfd41a0f475c547807ebcec8524d9605ab5c6de79f302fa" + } +} diff --git a/example/app.tsx b/example/app.tsx index 3d5d8ba..de7a991 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -35,8 +35,8 @@ export default ()=> const [Store, Dispatch] = React.useReducer(reducer, {name:"seth", age:24} as Store, builder) return -
-

Title!

+
+

Title

subtitle!

diff --git a/example/deno.json b/example/deno.json index 9f0718b..cef02df 100644 --- a/example/deno.json +++ b/example/deno.json @@ -3,7 +3,7 @@ "imports": { "react":"https://esm.sh/preact@10.15.1/compat", - "app": "./app.tsx" + "@app": "./app.tsx" }, "tasks": { diff --git a/serve.tsx b/serve.tsx index e224a03..132d72d 100644 --- a/serve.tsx +++ b/serve.tsx @@ -62,10 +62,8 @@ let Configuration:Configuration =

`, {status:200, headers:{"content-type":"text/html"}}); @@ -76,7 +74,7 @@ let Configuration:Configuration = minify: true, jsc: { - target:"es2017", + target:"es2022", minify: { compress: { unused: true }, -- 2.34.1 From e64ce9a144638173fdbff62cdb0de7f8f0610c0d Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Tue, 20 Jun 2023 10:49:47 -0400 Subject: [PATCH 23/25] mount fixes strange issue with createElement --- _lib_/mount.tsx | 40 +++++++++++++++++++--------------------- example/app.tsx | 6 +++--- serve.tsx | 4 ++-- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/_lib_/mount.tsx b/_lib_/mount.tsx index 3eaeac1..0314212 100644 --- a/_lib_/mount.tsx +++ b/_lib_/mount.tsx @@ -2,17 +2,16 @@ import React from "react"; import * as TW from "https://esm.sh/@twind/core@1.0.1"; import TWPreTail from "https://esm.sh/@twind/preset-tailwind@1.0.1"; import TWPreAuto from "https://esm.sh/@twind/preset-autoprefix@1.0.1"; -import react from "./react.tsx"; const Configure = { theme: {}, - presets:[TWPreTail(), TWPreAuto()] + presets: [TWPreTail(), TWPreAuto()], + hash: false } as TW.TwindUserConfig; export const Shadow =(inElement:HTMLElement, inConfig?:TW.TwindUserConfig)=> { - /* const merge = inConfig ? {...Configure, ...inConfig} : Configure; const ShadowDOM = inElement.attachShadow({ mode: "open" }); @@ -23,33 +22,32 @@ export const Shadow =(inElement:HTMLElement, inConfig?:TW.TwindUserConfig)=> ShadowDOM.append(ShadowDiv); TW.observe(TW.twind(merge, TW.cssom(ShadowCSS)), ShadowDiv); return ShadowDiv; - */ - - return inElement; - - }; -export const Render =async(inElement:HTMLElement, inApp:()=>React.JSX.Element)=> -{ - const wrapped = React.createElement(()=> React.createElement(inApp, null), null); +export default async(inSelector:string, inModulePath:string, inMemberApp="default", inMemberCSS="CSS"):Promise<(()=>void)|false>=> +{ + let dom = document.querySelector(inSelector); + if(!dom) + { + console.log(`element "${inSelector}" not found.`); + return false; + } + + const module = await import(inModulePath); + dom = Shadow(dom as HTMLElement, module[inMemberCSS]) + + const app = React.createElement(()=> React.createElement(module[inMemberApp], null), null); if(React.render) { - React.render(wrapped, inElement); - return ()=>React.unmountComponentAtNode(inElement); + React.render(app, dom); + return ()=>dom && React.unmountComponentAtNode(dom); } else { const reactDom = await import(`https://esm.sh/react-dom@${React.version}/client`); - const root = reactDom.createRoot(inElement); - root.render(wrapped); + const root = reactDom.createRoot(dom); + root.render(app); return root.unmount; } -}; -export default async(inSelector:string, inModulePath:string, inMemberApp="default", inMemberCSS="CSS"):Promise<()=>void|false>=> -{ - const members = await import(inModulePath); - const element = document.querySelector(inSelector); - return element ? await Render(Shadow(element as HTMLElement, members.CSS), members.default) : false; }; \ No newline at end of file diff --git a/example/app.tsx b/example/app.tsx index de7a991..716c553 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -4,7 +4,7 @@ const CTXString = React.createContext("lol"); type StateBinding = [get:T, set:React.StateUpdater]; const CTXState = React.createContext(null) as React.Context|null>; -const Outer =(props:{children:Preact.VNode})=> +const Outer =(props:{children:React.JSX.Element})=> { const binding = React.useState(11); return @@ -35,8 +35,8 @@ export default ()=> const [Store, Dispatch] = React.useReducer(reducer, {name:"seth", age:24} as Store, builder) return -
-

Title

+
+

Title!!!!

subtitle!

diff --git a/serve.tsx b/serve.tsx index 132d72d..a0a7dd1 100644 --- a/serve.tsx +++ b/serve.tsx @@ -62,8 +62,8 @@ let Configuration:Configuration =

`, {status:200, headers:{"content-type":"text/html"}}); -- 2.34.1 From 47ef75c55b762f40badfc143e04961a0c986aa94 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Tue, 20 Jun 2023 21:40:49 -0400 Subject: [PATCH 24/25] got it! --- _lib_/boot.tsx | 15 ++++++++++++++ _lib_/mount.tsx | 38 ++++++++++++++++++++++++++++++++- deno.lock | 23 -------------------- example/app.tsx | 9 ++++---- example/deno.json | 7 +++---- local.tsx | 53 +++++++++++++++++++++++++---------------------- serve.tsx | 43 +++++++++++++++++++++++++------------- 7 files changed, 117 insertions(+), 71 deletions(-) create mode 100644 _lib_/boot.tsx delete mode 100644 deno.lock diff --git a/_lib_/boot.tsx b/_lib_/boot.tsx new file mode 100644 index 0000000..c510a00 --- /dev/null +++ b/_lib_/boot.tsx @@ -0,0 +1,15 @@ +import "../serve.tsx"; + +Deno.args.forEach(arg=> +{ + if(arg.startsWith("--")) + { + const kvp = arg.substring(2).split("="); + Deno.env.set(kvp[0], kvp[1] || "true"); + } +}); + +if(Deno.env.get("dev")) +{ + await import("../local.tsx"); +} \ No newline at end of file diff --git a/_lib_/mount.tsx b/_lib_/mount.tsx index 0314212..ab5e1ca 100644 --- a/_lib_/mount.tsx +++ b/_lib_/mount.tsx @@ -24,6 +24,42 @@ export const Shadow =(inElement:HTMLElement, inConfig?:TW.TwindUserConfig)=> return ShadowDiv; }; +let booted = false; +export const Boot =async(inSettings:{App:()=>React.JSX.Element, CSS?:TW.TwindUserConfig, DOM?:string})=> +{ + if(booted){return;} + booted = true; + + const settings = {CSS:{...Configure, ...inSettings.CSS||{} }, DOM:inSettings.DOM||"#app", App:inSettings.App}; + + console.log("Clinet boot called") + + let dom = document.querySelector(settings.DOM); + if(!dom) + { + console.log(`element "${settings.DOM}" not found.`); + return false; + } + + dom = Shadow(dom as HTMLElement, settings.CSS) + + const app = React.createElement(()=> React.createElement(settings.App, null), null); + if(React.render) + { + React.render(app, dom); + return ()=>dom && React.unmountComponentAtNode(dom); + } + else + { + const reactDom = await import(`https://esm.sh/react-dom@${React.version}/client`); + const root = reactDom.createRoot(dom); + root.render(app); + return root.unmount; + } + +}; + + export default async(inSelector:string, inModulePath:string, inMemberApp="default", inMemberCSS="CSS"):Promise<(()=>void)|false>=> { let dom = document.querySelector(inSelector); @@ -50,4 +86,4 @@ export default async(inSelector:string, inModulePath:string, inMemberApp="defaul return root.unmount; } -}; \ No newline at end of file +}; diff --git a/deno.lock b/deno.lock deleted file mode 100644 index 6c863c0..0000000 --- a/deno.lock +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": "2", - "remote": { - "https://esm.sh/preact@10.15.1": "2b79349676a4942fbcf835c4efa909791c2f0aeca195225bf22bac9866e94b4e", - "https://esm.sh/preact@10.15.1/compat": "07273e22b1c335b8acc9f33c5e78165319c59bd8e2d0f3e5a2b4e028329424d9", - "https://esm.sh/react@18.2.0": "742d8246041966ba1137ec8c60888c35882a9d2478bce63583875f86c1e3687c", - "https://esm.sh/stable/preact@10.15.1/denonext/compat.js": "bad6b5b4d4fdfa5975b7a8d30410bd6877247f058e4952799fab39f66a94b8cf", - "https://esm.sh/stable/preact@10.15.1/denonext/hooks.js": "5c989ad368cf4f2cb3a5d7d1801843d9348c599fe3e7731d04728f7b845d724e", - "https://esm.sh/stable/preact@10.15.1/denonext/preact.mjs": "30710ac1d5ff3711ae0c04eddbeb706f34f82d97489f61aaf09897bc75d2a628", - "https://esm.sh/stable/react@18.2.0/deno/react.mjs": "a5a73ee24acca4744ee22c51d9357f31968d1f684ce253bde222b4e26d09f49f", - "https://esm.sh/v118/@types/prop-types@15.7.5/index.d.ts": "6a386ff939f180ae8ef064699d8b7b6e62bc2731a62d7fbf5e02589383838dea", - "https://esm.sh/v118/@types/react@18.2.0/global.d.ts": "549df62b64a71004aee17685b445a8289013daf96246ce4d9b087d13d7a27a61", - "https://esm.sh/v118/@types/react@18.2.0/index.d.ts": "b091747b1f503f434d3cac4217a13858baba87b421a7054ffdfd797da7737678", - "https://esm.sh/v118/@types/scheduler@0.16.3/tracing.d.ts": "f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5", - "https://esm.sh/v118/csstype@3.1.2/index.d.ts": "4c68749a564a6facdf675416d75789ee5a557afda8960e0803cf6711fa569288", - "https://esm.sh/v118/preact@10.15.1/compat/src/index.d.ts": "9ec63be9612a10ff72fd4183179cde7d551ce43b3a0c2f549d8f788910d8263d", - "https://esm.sh/v118/preact@10.15.1/compat/src/suspense-list.d.ts": "b8e274324392157ce476ef3a48ae215c9f7003b08525d140645f19eab20d1948", - "https://esm.sh/v118/preact@10.15.1/compat/src/suspense.d.ts": "81f5266e0977a94347505d11b8103024211f2b4f3b2eb2aa276a10d8fd169e65", - "https://esm.sh/v118/preact@10.15.1/hooks/src/index.d.ts": "933eab6436614f8cd8e9b7c9b8bd54c6f3f14c3f065ba42c8c87a42d93de6595", - "https://esm.sh/v118/preact@10.15.1/src/index.d.ts": "fa83186a4b6caca36d52ca2d49b481c3ca5460988d4a8388d44dadc28987fb27", - "https://esm.sh/v118/preact@10.15.1/src/jsx.d.ts": "a6e4b7e4af3b959f8cfd41a0f475c547807ebcec8524d9605ab5c6de79f302fa" - } -} diff --git a/example/app.tsx b/example/app.tsx index 716c553..eeeac98 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -1,3 +1,4 @@ +import "../_lib_/boot.tsx"; import React from "react"; const CTXString = React.createContext("lol"); @@ -30,13 +31,13 @@ const builder =(inState:Store):Store=> return inState; } + export default ()=> { - const [Store, Dispatch] = React.useReducer(reducer, {name:"seth", age:24} as Store, builder) return
-

Title!!!!

+

Title?

subtitle!

@@ -48,5 +49,5 @@ export default ()=> - -} \ No newline at end of file + ; +} diff --git a/example/deno.json b/example/deno.json index cef02df..e10cb86 100644 --- a/example/deno.json +++ b/example/deno.json @@ -2,12 +2,11 @@ "compilerOptions": { "lib": ["deno.window", "dom"] }, "imports": { - "react":"https://esm.sh/preact@10.15.1/compat", - "@app": "./app.tsx" + "react":"https://esm.sh/preact@10.15.1/compat" }, "tasks": { - "local": "deno run -A --no-lock ../local.tsx", - "serve": "deno run -A --no-lock ../serve.tsx" + "local": "deno run -A --no-lock app.tsx --dev", + "serve": "deno run -A --no-lock app.tsx" } } \ No newline at end of file diff --git a/local.tsx b/local.tsx index aee018c..055c59b 100644 --- a/local.tsx +++ b/local.tsx @@ -2,10 +2,8 @@ import {Configure, Transpile, Extension} from "./serve.tsx"; const SocketsLive:Set = new Set(); const SocketsSend =(inData:string)=>{ console.log(inData); for (const socket of SocketsLive){ socket.send(inData); } } -const Directory = `file://${Deno.cwd().replaceAll("\\", "/")}`; Configure({ - Proxy:Directory, SWCOp: { sourceMaps: "inline", @@ -36,11 +34,11 @@ Configure({ console.log(inImports); return inImports; }, - async Serve(inReq, inURL, inExt, inMap) + async Serve(inReq, inURL, inExt, inMap, inProxy) { if(Transpile.Check(inExt) && !inURL.searchParams.get("reload") && !inURL.pathname.startsWith("/_lib_/")) { - const imp = await import(Directory+inURL.pathname); + const imp = await import(inProxy+inURL.pathname); const members = []; for( const key in imp ) { members.push(key); } @@ -73,35 +71,40 @@ FileListen("${inURL.pathname}", (updatedModule)=> } }); -let blocking = false; -const filesChanged:Map = new Map(); -for await (const event of Deno.watchFs(Deno.cwd())) +const Watcher =async()=> { - event.paths.forEach( path => filesChanged.set(path, event.kind) ); - if(!blocking) + let blocking = false; + const filesChanged:Map = new Map(); + for await (const event of Deno.watchFs(Deno.cwd())) { - blocking = true; - setTimeout(async()=> + event.paths.forEach( path => filesChanged.set(path, event.kind) ); + if(!blocking) { - for await (const [path, action] of filesChanged) + blocking = true; + setTimeout(async()=> { - if(Transpile.Check(Extension(path))) + for await (const [path, action] of filesChanged) { - const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); - if(action != "remove") - { - const tsx = await Transpile.Fetch(Directory+key, key, true); - tsx && SocketsSend(key); - } - else + if(Transpile.Check(Extension(path))) { - Transpile.Cache.delete(key); + const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); + if(action != "remove") + { + const tsx = await Transpile.Fetch(`file://${Deno.cwd().replaceAll("\\", "/")}`+key, key, true); + tsx && SocketsSend(key); + } + else + { + Transpile.Cache.delete(key); + } } } + filesChanged.clear(); + blocking = false; } - filesChanged.clear(); - blocking = false; + , 1000); } - , 1000); } -} \ No newline at end of file +} + +Watcher().then(()=>console.log("done watching")); \ No newline at end of file diff --git a/serve.tsx b/serve.tsx index a0a7dd1..c3f06a0 100644 --- a/serve.tsx +++ b/serve.tsx @@ -23,16 +23,16 @@ const ImportMapReload =async()=> ImportMap.imports = Configuration.Remap(json.imports); }; -type CustomHTTPHandler = (inReq:Request, inURL:URL, inExt:string|false, inMap:{imports:Record})=>void|false|Response|Promise; +type CustomHTTPHandler = (inReq:Request, inURL:URL, inExt:string|false, inMap:{imports:Record}, inProxy:string)=>void|false|Response|Promise; type CustomRemapper = (inImports:Record)=>Record; type Configuration = {Proxy:string, Allow:string, Reset:string, SWCOp:SWCW.Options, Serve:CustomHTTPHandler, Shell:CustomHTTPHandler, Remap:CustomRemapper}; type ConfigurationArgs = {Proxy?:string, Allow?:string, Reset?:string, SWCOp?:SWCW.Options, Serve?:CustomHTTPHandler, Shell?:CustomHTTPHandler, Remap?:CustomRemapper}; let Configuration:Configuration = { - Proxy: `file://${Deno.cwd().replaceAll("\\", "/")}`, + Proxy: new URL(`file://${Deno.cwd().replaceAll("\\", "/")}`).toString(), Allow: "*", Reset: "/clear-cache", - Serve(inReq, inURL, inExt, inMap){}, + Serve(inReq, inURL, inExt, inMap, inProxy){}, Remap: (inImports)=> { Object.entries(inImports).forEach(([key, value])=> @@ -51,8 +51,13 @@ let Configuration:Configuration = console.log(inImports); return inImports; }, - Shell(inReq, inURL, inExt, inMap) + Shell(inReq, inURL, inExt, inMap, inProxy) { + console.log("Start app:", Deno.mainModule, "start dir", inProxy); + console.log("Split:", Deno.mainModule.split(inProxy) ); + + const parts = Deno.mainModule.split(inProxy); + return new Response( ` @@ -62,8 +67,8 @@ let Configuration:Configuration =

`, {status:200, headers:{"content-type":"text/html"}}); @@ -162,7 +167,7 @@ HTTP.serve(async(req: Request)=> } // allow for custom handler - const custom = await Configuration.Serve(req, url, ext, ImportMap); + const custom = await Configuration.Serve(req, url, ext, ImportMap, Configuration.Proxy); if(custom) { return custom; @@ -171,26 +176,36 @@ HTTP.serve(async(req: Request)=> // transpileable files if(Transpile.Check(ext)) { + let code; + let path; if(url.pathname.startsWith("/_lib_/")) { - const path = import.meta.url+"/.."+url.pathname; - const code = await Transpile.Fetch(path, url.pathname, true); - if(code) + if(url.pathname.endsWith("boot.tsx")) { - return new Response(code, {headers:{"content-type":"application/javascript"}}); + path = import.meta.url+"/../_lib_/mount.tsx"; } + else + { + path = import.meta.url+"/.."+url.pathname; + } + code = await Transpile.Fetch(path, url.pathname, true); } else { - const lookup = await Transpile.Fetch(Configuration.Proxy + url.pathname, url.pathname); - return new Response(lookup, {status:lookup?200:404, headers:{...headers, "content-type":"application/javascript"}} ); + path = Configuration.Proxy + url.pathname; + code = await Transpile.Fetch(path, url.pathname); } + + if(code) + { + return new Response(code, {headers:{...headers, "content-type":"application/javascript"}} ); + } } // custom page html if(!ext) { - const shell = await Configuration.Shell(req, url, ext, ImportMap); + const shell = await Configuration.Shell(req, url, ext, ImportMap, Configuration.Proxy); if(shell) { return shell; -- 2.34.1 From 0e5caeada7faa4c89b3c7f99ff37c55a3c2b5682 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Tue, 20 Jun 2023 22:56:20 -0400 Subject: [PATCH 25/25] remap imports --- deno.json | 2 +- example/app.tsx | 4 ++-- example/deno.json | 7 ++++--- local.tsx | 10 ---------- serve.tsx | 32 ++++++++++++++++++++++---------- 5 files changed, 29 insertions(+), 26 deletions(-) diff --git a/deno.json b/deno.json index 8350e18..b3f0c20 100644 --- a/deno.json +++ b/deno.json @@ -7,7 +7,7 @@ { "react":"https://esm.sh/preact@10.15.1/compat", "react-original":"https://esm.sh/preact@10.15.1/compat", - "@app": "./app.tsx" + "@able/": "./_lib_/" }, "tasks": { diff --git a/example/app.tsx b/example/app.tsx index eeeac98..40e1139 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -1,4 +1,4 @@ -import "../_lib_/boot.tsx"; +import "@able/boot.tsx"; import React from "react"; const CTXString = React.createContext("lol"); @@ -37,7 +37,7 @@ export default ()=> const [Store, Dispatch] = React.useReducer(reducer, {name:"seth", age:24} as Store, builder) return
-

Title?

+

Title????

subtitle!

diff --git a/example/deno.json b/example/deno.json index e10cb86..9ef9dc0 100644 --- a/example/deno.json +++ b/example/deno.json @@ -2,11 +2,12 @@ "compilerOptions": { "lib": ["deno.window", "dom"] }, "imports": { - "react":"https://esm.sh/preact@10.15.1/compat" + "react":"https://esm.sh/preact@10.15.1/compat", + "@able/":"http://localhost:4507/_lib_/" }, "tasks": { - "local": "deno run -A --no-lock app.tsx --dev", - "serve": "deno run -A --no-lock app.tsx" + "local": "deno run -A --no-lock --reload=http://localhost:4507 app.tsx --dev", + "serve": "deno run -A --no-lock --reload=http://localhost:4507 app.tsx" } } \ No newline at end of file diff --git a/local.tsx b/local.tsx index 055c59b..60d17d2 100644 --- a/local.tsx +++ b/local.tsx @@ -20,18 +20,8 @@ Configure({ }, Remap: (inImports)=> { - console.log("running remapper"); - Object.entries(inImports).forEach(([key, value])=> - { - if(value.startsWith("./")) - { - inImports[key] = value.substring(1); - } - }); - inImports["react-original"] = inImports["react"]; inImports["react"] = "/_lib_/react.tsx"; - console.log(inImports); return inImports; }, async Serve(inReq, inURL, inExt, inMap, inProxy) diff --git a/serve.tsx b/serve.tsx index c3f06a0..821c281 100644 --- a/serve.tsx +++ b/serve.tsx @@ -20,7 +20,27 @@ const ImportMapReload =async()=> console.log(`error reading deno config "${path}" message:"${e}"`); return; } + + Object.entries(json.imports).forEach(([key, value])=> + { + if(value.startsWith("./")) + { + json.imports[key] = value.substring(1); + } + }); + if(!json.imports["@able/"]) + { + console.log(`"@able/" specifier not defined in import map`); + } + json.imports["@able/"] = "/_lib_/"; + + if(!json.imports["react"]) + { + console.log(`"react" specifier not defined in import map`); + } + ImportMap.imports = Configuration.Remap(json.imports); + console.log(ImportMap.imports); }; type CustomHTTPHandler = (inReq:Request, inURL:URL, inExt:string|false, inMap:{imports:Record}, inProxy:string)=>void|false|Response|Promise; @@ -35,20 +55,12 @@ let Configuration:Configuration = Serve(inReq, inURL, inExt, inMap, inProxy){}, Remap: (inImports)=> { - Object.entries(inImports).forEach(([key, value])=> - { - if(value.startsWith("./")) - { - inImports[key] = value.substring(1); - } - }); - const reactURL = inImports["react"] ?? console.log("React is not defined in imports"); + const reactURL = inImports["react"]; const setting = Configuration.SWCOp?.jsc?.transform?.react; - if(setting) + if(setting && reactURL) { setting.importSource = reactURL; } - console.log(inImports); return inImports; }, Shell(inReq, inURL, inExt, inMap, inProxy) -- 2.34.1