From 9bc051c831cb333ee003ca6769564a305314b4bc Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 17 Mar 2023 11:54:36 -0400 Subject: [PATCH 1/9] separate websocket port --- deno.json | 3 +- deno.lock | 10 --- server.tsx | 240 +++++++++++++++++++++++++---------------------------- 3 files changed, 115 insertions(+), 138 deletions(-) delete mode 100644 deno.lock diff --git a/deno.json b/deno.json index df9bcff..c919db2 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,7 @@ { "tasks": { - "install": "deno install -f -A --unstable --no-lock -n tsx server.tsx" + "install": "deno install -f -A --unstable --no-lock -n tsx server.tsx", + "run": "deno run -A --unstable --no-lock server.tsx" } } \ No newline at end of file diff --git a/deno.lock b/deno.lock deleted file mode 100644 index f896ca5..0000000 --- a/deno.lock +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": "2", - "remote": { - "https://deno.land/std@0.151.0/async/debounce.ts": "564273ef242bcfcda19a439132f940db8694173abffc159ea34f07d18fc42620", - "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", - "https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4", - "https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d", - "https://deno.land/x/esbuild@v0.14.45/mod.js": "ab18347f6a57e56f776a9997a5b726f7f6c8d1f008f08e41e7273be8c31e12f4" - } -} diff --git a/server.tsx b/server.tsx index 2664a9a..e86a661 100644 --- a/server.tsx +++ b/server.tsx @@ -1,108 +1,77 @@ import * as ESBuild from 'https://deno.land/x/esbuild@v0.14.45/mod.js'; +import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts"; import { debounce } from "https://deno.land/std@0.151.0/async/debounce.ts"; + console.log(`Serving files from "${Deno.cwd()}"`); -const MIME:Record = { - ".aac": "audio/aac", - ".abw": "application/x-abiword", - ".arc": "application/x-freearc", - ".avif": "image/avif", - ".avi": "video/x-msvideo", - ".azw": "application/vnd.amazon.ebook", - ".bin": "application/octet-stream", - ".bmp": "image/bmp", - ".bz": "application/x-bzip", - ".bz2": "application/x-bzip2", - ".cda": "application/x-cdf", - ".csh": "application/x-csh", - ".css": "text/css", - ".csv": "text/csv", - ".doc": "application/msword", - ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - ".eot": "application/vnd.ms-fontobject", - ".epub": "application/epub+zip", - ".gz": "application/gzip", - ".gif": "image/gif", - ".htm": "text/html", - ".html": "text/html", - ".ico": "image/x-icon", - ".ics": "text/calendar", - ".jar": "application/java-archive", - ".jpeg": "image/jpeg", - ".jpg": "image/jpeg", - ".js": "application/javascript", - ".jsx": "application/javascript", - ".json": "application/json", - ".jsonld": "application/ld+json", - ".mid": "audio/midi", - ".midi": "audio/midi", - ".mjs": "text/javascript", - ".mp3": "audio/mpeg", - ".mp4": "video/mp4", - ".mpeg": "video/mpeg", - ".mpkg": "application/vnd.apple.installer+xml", - ".odp": "application/vnd.oasis.opendocument.presentation", - ".ods": "application/vnd.oasis.opendocument.spreadsheet", - ".odt": "application/vnd.oasis.opendocument.text", - ".oga": "audio/ogg", - ".ogv": "video/ogg", - ".ogx": "application/ogg", - ".opus": "audio/opus", - ".otf": "font/otf", - ".png": "image/png", - ".pdf": "application/pdf", - ".php": "application/x-httpd-php", - ".ppt": "application/vnd.ms-powerpoint", - ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", - ".rar": "application/vnd.rar", - ".rtf": "application/rtf", - ".sh": "application/x-sh", - ".svg": "image/svg+xml", - ".swf": "application/x-shockwave-flash", - ".tar": "application/x-tar", - ".tif": "image/tiff", - ".tiff": "image/tiff", - ".ts": "application/javascript", - ".tsx": "application/javascript", - ".ttf": "font/ttf", - ".txt": "text/plain", - ".vsd": "application/vnd.visio", - ".wav": "audio/wav", - ".weba": "audio/webm", - ".webm": "video/webm", - ".webp": "image/webp", - ".woff": "font/woff", - ".woff2": "font/woff2", - ".xhtml": "application/xhtml+xml", - ".xls": "application/vnd.ms-excel", - ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ".xml": "application/xml", - ".xul": "application/vnd.mozilla.xul+xml", - ".zip": "application/zip", - ".3gp": "video/3gpp", - ".3g2": "video/3gpp2", - ".7z": "application/x-7z-compressed" -}; -const Type =(inPath:string):string|undefined=> -{ - const dot:number = inPath.lastIndexOf("."); - const ext = dot > -1 ? inPath.substring(dot) : ".html"; - return MIME[ext]; -}; - const Transpiled = new Map(); -const Transpile =async(inPath:string, inKey:string):Promise=> +/** + * checks for (.tsx | .jsx | .ts | .js) extensions + */ +export const Transpileable =(inFilePath:string):boolean=> +{ + let dotIndex = inFilePath.length-4; + if(inFilePath[dotIndex] !== ".") + { + if(inFilePath[++dotIndex] !== ".") + { + return false; + } + } + + if(inFilePath[dotIndex+2] == "s") + { + const first = inFilePath[dotIndex+1]; + return (first == "t" || first == "j"); + } + + return false; +}; +export const Transpile =async(inPath:string, inKey:string):Promise=> { const body = await Deno.readTextFile(inPath); - const transpile = await ESBuild.transform(body, { loader: "tsx", sourcemap: "inline" }); + const transpile = await ESBuild.transform(body, { loader: "tsx", sourcemap: "inline", minify:true }); Transpiled.set(inKey, transpile.code); return transpile.code; }; -const confDeno = await Deno.readTextFile(Deno.cwd()+"/deno.json"); -const pathImport = JSON.parse(confDeno).importMap; -const confImports = await Deno.readTextFile(pathImport); +let ImportString = ``; +let ImportObject = {}; +try +{ + const confDeno = await Deno.readTextFile(Deno.cwd()+"/deno.json"); + const confDenoParsed:{importMap?:string, imports?:string} = JSON.parse(confDeno); + if(confDenoParsed.importMap) + { + try + { + const confImports = await Deno.readTextFile(confDenoParsed.importMap); + try + { + ImportObject = JSON.parse(confImports); + ImportString = confImports; + } + catch(e) + { + console.log(`importMap "${confDenoParsed.importMap}" contains invalid JSON`); + } + } + catch(e) + { + console.log(`importMap "${confDenoParsed.importMap}" cannot be found`); + } + } + else if(confDenoParsed.imports) + { + ImportObject = {imports:confDenoParsed.imports}; + ImportString = JSON.stringify(ImportObject); + } +} +catch(e) +{ + console.log(`deno.json not found`); +} const Index = ` @@ -111,7 +80,7 @@ const Index = `
Loading
- +
Loading
@@ -109,43 +137,6 @@ Deno.serve({ port: 3000 }, async(_req:Request) => console.log(`Request for "${url.pathname}"...`); - try - { - // serve index by default - let type = `text/html`; - let body:BodyInit = Index; - - // serve .tsx .jsx .ts .js - if(Transpileable(url.pathname)) - { - body = Transpiled.get(url.pathname); - type = `application/javascript`; - if(!body) - { - body = await Transpile(fsPath, url.pathname); - } - } - // serve static media - else if( url.pathname.endsWith("/") === false) - { - body = await Deno.readFile(fsPath); - type = MIME.typeByExtension(url.pathname.substring(url.pathname.lastIndexOf("."))) || "text/html"; - } - - return new Response(body, {headers:{"content-type":type as string}}); - } - catch(error) - { - console.log(` ...404`); - return new Response(error, {status:404}); - } -}); - - -const Sockets:Set = new Set(); -const SocketsBroadcast =(inData:string)=>{ for (const socket of Sockets){ socket.send(inData); } } -Deno.serve({ port: 3001 }, (_req:Request) => -{ if(_req.headers.get("upgrade") == "websocket") { try @@ -170,8 +161,60 @@ Deno.serve({ port: 3001 }, (_req:Request) => // } } - return new Response("Overwatch: Not a websocket request."); + + try + { + // serve index by default + let type = `text/html`; + let body:BodyInit = Index; + + // serve .tsx .jsx .ts .js + if(Transpileable(url.pathname)) + { + type = `application/javascript`; + if(!url.searchParams.get("reload")) + { + const path = `file://${Deno.cwd().replaceAll("\\", "/")+url.pathname}`; + console.log(path); + + const imp = await import(path); + const members = []; + for( const key in imp ) { members.push(key); } + body = +`import * as Import from "${url.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") } +}; +window.HMR("${url.pathname}", reloadHandler);`; + + } + else + { + body = Transpiled.get(url.pathname) || await Transpile(fsPath, url.pathname); + } + } + // serve static media + else if( url.pathname.endsWith("/") === false) + { + body = await Deno.readFile(fsPath); + type = MIME.typeByExtension(url.pathname.substring(url.pathname.lastIndexOf("."))) || "text/html"; + } + + return new Response(body, {headers:{"content-type":type as string}}); + } + catch(error) + { + console.log(` ...404`); + return new Response(error, {status:404}); + } }); + + +const Sockets:Set = new Set(); +const SocketsBroadcast =(inData:string)=>{ for (const socket of Sockets){ socket.send(inData); } } + const FilesChanged:Map = new Map(); const ProcessFiles =debounce(async()=> { diff --git a/test.tsx b/test.tsx new file mode 100644 index 0000000..e43494b --- /dev/null +++ b/test.tsx @@ -0,0 +1 @@ +export const test = "true"; \ No newline at end of file From 08ecdfa6f054cd1d837d327e7fef8acf5897d509 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Wed, 22 Mar 2023 17:45:25 -0400 Subject: [PATCH 3/9] serving adjacent files --- hmr/react.tsx | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++ server.tsx | 105 ++++++++++++++++++++++++++------------- 2 files changed, 203 insertions(+), 34 deletions(-) create mode 100644 hmr/react.tsx diff --git a/hmr/react.tsx b/hmr/react.tsx new file mode 100644 index 0000000..3647b1f --- /dev/null +++ b/hmr/react.tsx @@ -0,0 +1,132 @@ +import * as ReactParts from "react-original"; + +const H = ReactParts.createElement; + +const HMR = { + registered: new Map(), + states: new Map(), + statesOld: new Map(), + reloads: 0, + wireframe: false +}; +HMR.onChange =(key, value)=> +{ + HMR.registered.set(key, value); +}; +HMR.update =()=> +{ + HMR.reloads++; + const keys = []; + for(const [key, value] of HMR.registered){ keys.push(key); } + HMR.registered.clear(); + HMR.statesOld = HMR.states; + HMR.states = new Map(); + keys.forEach(k=>k()); + HMR.echoState(); +}; +HMR.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); +}; +HMR.wipe =()=> +{ + HMR.statesOld = new Map(); +}; + +const MapAt =(inMap, inIndex)=> +{ + let index = 0; + for(const kvp of inMap) + { + if(index == inIndex) + { + return kvp; + } + index++; + } + return false; +} + +window.HMR = HMR; + +const ProxyElement = (props)=> +{ + const [stateGet, stateSet] = ReactParts.useState(0); + ReactParts.useEffect(()=>HMR.onChange( ()=>stateSet(stateGet+1), "ProxyElement" )); + + 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-alias"; +export { ProxyCreate as createElement, ProxyState as useState }; +export default {...ReactParts.default, createElement:ProxyCreate, useState:ProxyState}; \ No newline at end of file diff --git a/server.tsx b/server.tsx index 85405c7..cdb2d4d 100644 --- a/server.tsx +++ b/server.tsx @@ -34,13 +34,13 @@ export const Transpile =async(inPath:string, inKey:string):Promise=> }; +type ImportMap = {imports?:Record, importMap?:string}; let ImportString = ``; -let ImportObject = {}; +let ImportObject:ImportMap = {}; try { - const confDeno = await Deno.readTextFile(Deno.cwd()+"/deno.json"); - const confDenoParsed:{importMap?:string, imports?:string} = JSON.parse(confDeno); + const confDenoParsed:ImportMap = JSON.parse(confDeno); if(confDenoParsed.importMap) { try @@ -53,12 +53,12 @@ try } catch(e) { - console.log(`importMap "${confDenoParsed.importMap}" contains invalid JSON`); + console.log(`"importMap" "${confDenoParsed.importMap}" contains invalid JSON`); } } catch(e) { - console.log(`importMap "${confDenoParsed.importMap}" cannot be found`); + console.log(`"importMap" "${confDenoParsed.importMap}" cannot be found`); } } else if(confDenoParsed.imports) @@ -66,11 +66,33 @@ try ImportObject = {imports:confDenoParsed.imports}; ImportString = JSON.stringify(ImportObject); } + + if(ImportObject.imports) + { + const importReact = ImportObject.imports?.["react"]; + if(importReact) + { + ImportObject.imports["react-original"] = importReact; + ImportObject.imports["react"] = "/hmr/react.tsx"; + ImportString = JSON.stringify(ImportObject); + } + else + { + console.log(`"imports" configuration does not alias "react"`); + } + } + else + { + console.log(`No "imports" found in configuration`); + } + } catch(e) { console.log(`deno.json not found`); } + + const Index = ` @@ -130,6 +152,9 @@ window.HMR = (path, handler)=> `; +const path = import.meta.resolve(`./hmr/react.sx`); +console.log("meta path", path); + Deno.serve({ port: 3000 }, async(_req:Request) => { const url:URL = new URL(_req.url); @@ -168,38 +193,50 @@ Deno.serve({ port: 3000 }, async(_req:Request) => let type = `text/html`; let body:BodyInit = Index; - // serve .tsx .jsx .ts .js - if(Transpileable(url.pathname)) + if(url.pathname.startsWith("/hmr/")) { - type = `application/javascript`; - if(!url.searchParams.get("reload")) - { - const path = `file://${Deno.cwd().replaceAll("\\", "/")+url.pathname}`; - console.log(path); - - const imp = await import(path); - const members = []; - for( const key in imp ) { members.push(key); } - body = -`import * as Import from "${url.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") } -}; -window.HMR("${url.pathname}", reloadHandler);`; - - } - else - { - body = Transpiled.get(url.pathname) || await Transpile(fsPath, url.pathname); - } + const path = import.meta.resolve(`.${url.pathname}`); + console.log("import path", path); + const resp = await fetch(path); + body = await resp.text(); + //body = Transpiled.get(url.pathname) || await Transpile("."+url.pathname, url.pathname); } - // serve static media - else if( url.pathname.endsWith("/") === false) + else { - body = await Deno.readFile(fsPath); - type = MIME.typeByExtension(url.pathname.substring(url.pathname.lastIndexOf("."))) || "text/html"; + // serve .tsx .jsx .ts .js + if(Transpileable(url.pathname)) + { + type = `application/javascript`; + if(!url.searchParams.get("reload")) + { + const path = `file://${Deno.cwd().replaceAll("\\", "/")+url.pathname}`; + console.log(path); + + const imp = await import(path); + const members = []; + for( const key in imp ) { members.push(key); } + body = + `import * as Import from "${url.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") } + }; + window.HMR("${url.pathname}", reloadHandler);`; + + } + else + { + body = Transpiled.get(url.pathname) || await Transpile(fsPath, url.pathname); + } + } + // serve static media + else if( url.pathname.endsWith("/") === false) + { + body = await Deno.readFile(fsPath); + type = MIME.typeByExtension(url.pathname.substring(url.pathname.lastIndexOf("."))) || "text/html"; + } + } return new Response(body, {headers:{"content-type":type as string}}); From 0186e40e6f1fee297a358e2a97075da637179dda Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Mon, 27 Mar 2023 23:05:20 -0400 Subject: [PATCH 4/9] use /lib/ as a general location --- {hmr => lib}/react.tsx | 2 +- server.tsx | 118 +++++++++++++++++++++++++---------------- 2 files changed, 74 insertions(+), 46 deletions(-) rename {hmr => lib}/react.tsx (98%) diff --git a/hmr/react.tsx b/lib/react.tsx similarity index 98% rename from hmr/react.tsx rename to lib/react.tsx index 3647b1f..4e90236 100644 --- a/hmr/react.tsx +++ b/lib/react.tsx @@ -127,6 +127,6 @@ const ProxyState =(arg)=> }; -export * from "react-alias"; +export * from "react-original"; export { ProxyCreate as createElement, ProxyState as useState }; export default {...ReactParts.default, createElement:ProxyCreate, useState:ProxyState}; \ No newline at end of file diff --git a/server.tsx b/server.tsx index cdb2d4d..bfa0353 100644 --- a/server.tsx +++ b/server.tsx @@ -25,13 +25,42 @@ export const Transpileable =(inFilePath:string):boolean=> return false; }; -export const Transpile =async(inPath:string, inKey:string):Promise=> +export const Transpile =async(inCode:string, inKey:string):Promise=> { - const body = await Deno.readTextFile(inPath); - const transpile = await ESBuild.transform(body, { loader: "tsx", sourcemap: "inline", minify:true }); + const transpile = await ESBuild.transform(inCode, { loader: "tsx", sourcemap: "inline", minify:true }); Transpiled.set(inKey, transpile.code); return transpile.code; }; +type Transpiler = (inPath:string, inKey:string, inCheck?:boolean)=>Promise; +export const TranspileFS:Transpiler =async(inPath, inKey, inCheck)=> +{ + if(inCheck) + { + const cached = Transpiled.get(inKey); + if(cached) + { + return cached; + } + } + const body = await Deno.readTextFile(inPath); + return Transpile(body, inKey); +}; +export const TranspileURL:Transpiler =async(inPath, inKey, inCheck)=> +{ + if(inCheck) + { + const cached = Transpiled.get(inKey); + if(cached) + { + return cached; + } + } + const path = import.meta.resolve(`.${inPath}`); + let body = await fetch(path); + let text = await body.text(); + return Transpile(text, inKey); +}; + type ImportMap = {imports?:Record, importMap?:string}; @@ -152,9 +181,6 @@ window.HMR = (path, handler)=> `; -const path = import.meta.resolve(`./hmr/react.sx`); -console.log("meta path", path); - Deno.serve({ port: 3000 }, async(_req:Request) => { const url:URL = new URL(_req.url); @@ -193,50 +219,52 @@ Deno.serve({ port: 3000 }, async(_req:Request) => let type = `text/html`; let body:BodyInit = Index; - if(url.pathname.startsWith("/hmr/")) + const isLib = url.pathname.startsWith("/lib/") + + // serve .tsx .jsx .ts .js + if(Transpileable(url.pathname)) { - const path = import.meta.resolve(`.${url.pathname}`); - console.log("import path", path); - const resp = await fetch(path); - body = await resp.text(); - //body = Transpiled.get(url.pathname) || await Transpile("."+url.pathname, url.pathname); - } - else - { - // serve .tsx .jsx .ts .js - if(Transpileable(url.pathname)) + type = `application/javascript`; + if(isLib) { - type = `application/javascript`; - if(!url.searchParams.get("reload")) - { - const path = `file://${Deno.cwd().replaceAll("\\", "/")+url.pathname}`; - console.log(path); - - const imp = await import(path); - const members = []; - for( const key in imp ) { members.push(key); } - body = - `import * as Import from "${url.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") } - }; - window.HMR("${url.pathname}", reloadHandler);`; - - } - else - { - body = Transpiled.get(url.pathname) || await Transpile(fsPath, url.pathname); - } + body = await TranspileURL(url.pathname, url.pathname, true); } - // serve static media - else if( url.pathname.endsWith("/") === false) + else if(!url.searchParams.get("reload")) + { + const path = `file://${Deno.cwd().replaceAll("\\", "/")+url.pathname}`; + console.log(path); + + const imp = await import(path); + const members = []; + for( const key in imp ) { members.push(key); } + body = +`import * as Import from "${url.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") } +}; +window.HMR("${url.pathname}", reloadHandler);`; + + } + else + { + body = await TranspileFS(fsPath, url.pathname, true); + } + } + // serve static media + else if( url.pathname.endsWith("/") === false) + { + type = MIME.typeByExtension(url.pathname.substring(url.pathname.lastIndexOf("."))) || "text/html"; + if(isLib) + { + const _fetch = await fetch(import.meta.resolve(`.${url.pathname}`)); + body = await _fetch.text(); + } + else { body = await Deno.readFile(fsPath); - type = MIME.typeByExtension(url.pathname.substring(url.pathname.lastIndexOf("."))) || "text/html"; } - } return new Response(body, {headers:{"content-type":type as string}}); @@ -264,7 +292,7 @@ const ProcessFiles =debounce(async()=> { if(action !== "remove") { - await Transpile(file, pathname); + await TranspileFS(file, pathname, false); console.log(` ...cached "${pathname}"`); SocketsBroadcast(pathname); } From 68b747c9ffef37c3cedf4d890a881e772991d94c Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Wed, 29 Mar 2023 22:43:23 -0400 Subject: [PATCH 5/9] hmr started there are issues with the dynamic import --- deep/file.txt | 0 deno.json | 6 ++++- deno.map.json | 5 ++++ lib/hmr.tsx | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/react.tsx | 62 +++--------------------------------------------- server.tsx | 30 ++---------------------- 6 files changed, 80 insertions(+), 88 deletions(-) delete mode 100644 deep/file.txt create mode 100644 deno.map.json create mode 100644 lib/hmr.tsx diff --git a/deep/file.txt b/deep/file.txt deleted file mode 100644 index e69de29..0000000 diff --git a/deno.json b/deno.json index c919db2..609c53c 100644 --- a/deno.json +++ b/deno.json @@ -1,7 +1,11 @@ { + "compilerOptions": {"lib": [ + "deno.window", "DOM" + ]}, + "importMap": "deno.map.json", "tasks": { - "install": "deno install -f -A --unstable --no-lock -n tsx server.tsx", + "install": "deno install -f -A --unstable --no-lock -n eno server.tsx", "run": "deno run -A --unstable --no-lock server.tsx" } } \ No newline at end of file diff --git a/deno.map.json b/deno.map.json new file mode 100644 index 0000000..0d471ef --- /dev/null +++ b/deno.map.json @@ -0,0 +1,5 @@ +{ + "imports": { + "react-original": "https://esm.sh/react@18.2.0" + } +} \ No newline at end of file diff --git a/lib/hmr.tsx b/lib/hmr.tsx new file mode 100644 index 0000000..56740bc --- /dev/null +++ b/lib/hmr.tsx @@ -0,0 +1,65 @@ + +let reloads = 0; +const listeners = new Map(); +new WebSocket("ws://"+document.location.host).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); +}); + +export const HMR = { + registered: new Map() as Mapvoid>, + states: new Map(), + statesOld: new Map(), + reloads: 0, + 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 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/lib/react.tsx b/lib/react.tsx index 4e90236..71780c7 100644 --- a/lib/react.tsx +++ b/lib/react.tsx @@ -1,69 +1,13 @@ import * as ReactParts from "react-original"; +import { HMR, MapAt } from "./hmr.tsx"; const H = ReactParts.createElement; -const HMR = { - registered: new Map(), - states: new Map(), - statesOld: new Map(), - reloads: 0, - wireframe: false -}; -HMR.onChange =(key, value)=> -{ - HMR.registered.set(key, value); -}; -HMR.update =()=> -{ - HMR.reloads++; - const keys = []; - for(const [key, value] of HMR.registered){ keys.push(key); } - HMR.registered.clear(); - HMR.statesOld = HMR.states; - HMR.states = new Map(); - keys.forEach(k=>k()); - HMR.echoState(); -}; -HMR.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); -}; -HMR.wipe =()=> -{ - HMR.statesOld = new Map(); -}; - -const MapAt =(inMap, inIndex)=> -{ - let index = 0; - for(const kvp of inMap) - { - if(index == inIndex) - { - return kvp; - } - index++; - } - return false; -} - -window.HMR = HMR; - const ProxyElement = (props)=> { + const id = ReactParts.useId(); const [stateGet, stateSet] = ReactParts.useState(0); - ReactParts.useEffect(()=>HMR.onChange( ()=>stateSet(stateGet+1), "ProxyElement" )); + ReactParts.useEffect(()=>HMR.onChange(id, ()=>stateSet(stateGet+1))); const child = H(...props.__args); diff --git a/server.tsx b/server.tsx index bfa0353..0a4ecac 100644 --- a/server.tsx +++ b/server.tsx @@ -102,7 +102,7 @@ try if(importReact) { ImportObject.imports["react-original"] = importReact; - ImportObject.imports["react"] = "/hmr/react.tsx"; + ImportObject.imports["react"] = "/lib/react.tsx"; ImportString = JSON.stringify(ImportObject); } else @@ -128,33 +128,7 @@ const Index = ` @@ -271,7 +245,7 @@ window.HMR("${url.pathname}", reloadHandler);`; } catch(error) { - console.log(` ...404`); + console.log(error); return new Response(error, {status:404}); } }); From 7e6377d5efb0d4165068f6fc6c40bfd6e309be3f Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 31 Mar 2023 06:55:54 -0400 Subject: [PATCH 6/9] more progress --- deno.json | 7 +++++-- deno.map.json | 5 ----- lib/hmr.tsx | 19 +++++++++++++------ runner.tsx | 7 ------- server.tsx | 17 +++++++++++++---- test.tsx | 1 - 6 files changed, 31 insertions(+), 25 deletions(-) delete mode 100644 deno.map.json delete mode 100644 runner.tsx delete mode 100644 test.tsx diff --git a/deno.json b/deno.json index 609c53c..2e7b733 100644 --- a/deno.json +++ b/deno.json @@ -2,10 +2,13 @@ "compilerOptions": {"lib": [ "deno.window", "DOM" ]}, - "importMap": "deno.map.json", + "imports": { + "react-original": "https://esm.sh/react@18.2.0" + }, "tasks": { "install": "deno install -f -A --unstable --no-lock -n eno server.tsx", - "run": "deno run -A --unstable --no-lock server.tsx" + "run": "deno run -A --unstable --no-lock server.tsx", + "host": "deno run -A --unstable https://deno.land/std@0.181.0/http/file_server.ts" } } \ No newline at end of file diff --git a/deno.map.json b/deno.map.json deleted file mode 100644 index 0d471ef..0000000 --- a/deno.map.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "imports": { - "react-original": "https://esm.sh/react@18.2.0" - } -} \ No newline at end of file diff --git a/lib/hmr.tsx b/lib/hmr.tsx index 56740bc..2aeb1fd 100644 --- a/lib/hmr.tsx +++ b/lib/hmr.tsx @@ -1,6 +1,6 @@ let reloads = 0; -const listeners = new Map(); +const listeners = new Map() as Mapvoid>>; new WebSocket("ws://"+document.location.host).addEventListener('message', (event) => { let handlers = listeners.get(event.data)??[]; @@ -11,22 +11,27 @@ new WebSocket("ws://"+document.location.host).addEventListener('message', (event return import(event.data+"?reload="+reloads) .then(updatedModule=>handler(updatedModule)); }) - ).then(HMR.update); + ).then(()=>HMR.update()); }); -export const HMR = { +export const FileListen =(inPath:string, inHandler:()=>void)=> +{ + const members = listeners.get(inPath)??[]; + members.push(inHandler); + listeners.set(inPath, members); +}; + +const HMR = { registered: new Map() as Mapvoid>, states: new Map(), statesOld: new Map(), - reloads: 0, - wireframe: false, + wireframe: true, 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; @@ -50,6 +55,8 @@ export const HMR = { } }; +export {HMR}; + export const MapAt =(inMap, inIndex)=> { let index = 0; diff --git a/runner.tsx b/runner.tsx deleted file mode 100644 index f6b7cfa..0000000 --- a/runner.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import * as FS from "https://deno.land/std@0.144.0/fs/mod.ts"; - -console.log(Deno.args, import.meta.url); -/* -await Deno.mkdir("deep"); -await Deno.create(`deep/file.txt`, "sup"); -*/ \ No newline at end of file diff --git a/server.tsx b/server.tsx index 0a4ecac..439de73 100644 --- a/server.tsx +++ b/server.tsx @@ -103,6 +103,7 @@ try { ImportObject.imports["react-original"] = importReact; ImportObject.imports["react"] = "/lib/react.tsx"; + ImportObject.imports["hmr"] = "/lib/hmr.tsx"; ImportString = JSON.stringify(ImportObject); } else @@ -147,9 +148,13 @@ const Index = ` ShadowDOM.append(ShadowCSS); ShadowDOM.append(ShadowDiv); - import App from "./app.tsx"; App(ShadowDiv, Configure); TW.observe(TW.twind(Configure, TW.cssom(ShadowCSS)), ShadowDiv); + + import App from "./app.tsx"; + import React from "react"; + React.render(React.createElement(App), ShadowDiv); + @@ -212,13 +217,17 @@ Deno.serve({ port: 3000 }, async(_req:Request) => const members = []; for( const key in imp ) { members.push(key); } body = -`import * as Import from "${url.pathname}?reload=0"; -${ members.map(m=>`let proxy_${m} = Import.${m}; export { proxy_${m} as ${m} };`).join(" ") } +` +import {FileListen} from "hmr"; +import * as Import from "${url.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") } }; -window.HMR("${url.pathname}", reloadHandler);`; +FileListen("${url.pathname}", reloadHandler);`; } else diff --git a/test.tsx b/test.tsx deleted file mode 100644 index e43494b..0000000 --- a/test.tsx +++ /dev/null @@ -1 +0,0 @@ -export const test = "true"; \ No newline at end of file From 4039a8b926c3c12416f8c5112b2a10f4e83760c5 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 31 Mar 2023 20:16:23 -0400 Subject: [PATCH 7/9] hmr working! --- lib/hmr.tsx | 4 +++- server.tsx | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/hmr.tsx b/lib/hmr.tsx index 2aeb1fd..59e1d1d 100644 --- a/lib/hmr.tsx +++ b/lib/hmr.tsx @@ -22,16 +22,18 @@ export const FileListen =(inPath:string, inHandler:()=>void)=> }; const HMR = { + reloads:0, registered: new Map() as Mapvoid>, states: new Map(), statesOld: new Map(), - wireframe: true, + 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; diff --git a/server.tsx b/server.tsx index 439de73..8bac226 100644 --- a/server.tsx +++ b/server.tsx @@ -148,12 +148,11 @@ const Index = ` ShadowDOM.append(ShadowCSS); ShadowDOM.append(ShadowDiv); - App(ShadowDiv, Configure); TW.observe(TW.twind(Configure, TW.cssom(ShadowCSS)), ShadowDiv); import App from "./app.tsx"; - import React from "react"; - React.render(React.createElement(App), ShadowDiv); + import {render, createElement as H} from "react"; + render(H(()=>H(App)), ShadowDiv); From 85bfaf5386e77068ffe1da834506fcb2fa467c55 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Sat, 1 Apr 2023 08:44:39 -0400 Subject: [PATCH 8/9] variablaize lib path --- server.tsx | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/server.tsx b/server.tsx index 8bac226..b9fa4c0 100644 --- a/server.tsx +++ b/server.tsx @@ -3,10 +3,7 @@ import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts"; import { debounce } from "https://deno.land/std@0.151.0/async/debounce.ts"; const Transpiled = new Map(); -/** - * checks for (.tsx | .jsx | .ts | .js) extensions - */ -export const Transpileable =(inFilePath:string):boolean=> +const Transpileable =(inFilePath:string):boolean=> { let dotIndex = inFilePath.length-4; if(inFilePath[dotIndex] !== ".") @@ -25,14 +22,14 @@ export const Transpileable =(inFilePath:string):boolean=> return false; }; -export const Transpile =async(inCode:string, inKey:string):Promise=> +const Transpile =async(inCode:string, inKey:string):Promise=> { const transpile = await ESBuild.transform(inCode, { loader: "tsx", sourcemap: "inline", minify:true }); Transpiled.set(inKey, transpile.code); return transpile.code; }; type Transpiler = (inPath:string, inKey:string, inCheck?:boolean)=>Promise; -export const TranspileFS:Transpiler =async(inPath, inKey, inCheck)=> +const TranspileFS:Transpiler =async(inPath, inKey, inCheck)=> { if(inCheck) { @@ -45,7 +42,7 @@ export const TranspileFS:Transpiler =async(inPath, inKey, inCheck)=> const body = await Deno.readTextFile(inPath); return Transpile(body, inKey); }; -export const TranspileURL:Transpiler =async(inPath, inKey, inCheck)=> +const TranspileURL:Transpiler =async(inPath, inKey, inCheck)=> { if(inCheck) { @@ -62,7 +59,7 @@ export const TranspileURL:Transpiler =async(inPath, inKey, inCheck)=> }; - +const LibPath = "lib"; type ImportMap = {imports?:Record, importMap?:string}; let ImportString = ``; let ImportObject:ImportMap = {}; @@ -102,8 +99,7 @@ try if(importReact) { ImportObject.imports["react-original"] = importReact; - ImportObject.imports["react"] = "/lib/react.tsx"; - ImportObject.imports["hmr"] = "/lib/hmr.tsx"; + ImportObject.imports["react"] = `./${LibPath}/react.tsx`; ImportString = JSON.stringify(ImportObject); } else @@ -121,21 +117,16 @@ catch(e) { console.log(`deno.json not found`); } - - const Index = ` - + +
Loading
- -