diff --git a/server.tsx b/server.tsx index fd59860..29bb34b 100644 --- a/server.tsx +++ b/server.tsx @@ -8,60 +8,132 @@ import * as Twind from "https://esm.sh/@twind/core@1.1.3"; import React from "react"; import * as Iso from "@eno/iso"; -let hosted = import.meta.resolve("./"); -const Path = { - Hosted:hosted.substring(0, hosted.length-1), - Active:`file://${Deno.cwd().replaceAll("\\", "/")}` -}; -console.log(Path); - -const Transpiled = new Map(); -const Transpileable =(inFilePath:string):boolean=> +/** + * Setup a transpiler. + * @param inDevMode When true, starts a file-watcher + * @returns + */ +function Transpiler(inDevMode:boolean) { - let dotIndex = inFilePath.length-4; - if(inFilePath[dotIndex] !== ".") + const Transpiled = new Map(); + const Transpileable =(inFilePath:string):boolean=> { - if(inFilePath[++dotIndex] !== ".") + let dotIndex = inFilePath.length-4; + if(inFilePath[dotIndex] !== ".") { - return false; + if(inFilePath[++dotIndex] !== ".") + { + return false; + } + } + + if(inFilePath[dotIndex+2] == "s") + { + const first = inFilePath[dotIndex+1]; + return (first == "t" || first == "j"); + } + + return false; + }; + const Transpile =async(inCode:string, inKey:string):Promise=> + { + const transpile = await ESBuild.transform(inCode, inDevMode ? { loader: "tsx", sourcemap:"inline", minify: false, sourcefile:inKey} : {loader:"tsx", minify: true}); + + Transpiled.set(inKey, transpile.code); + return transpile.code; + }; + type Transpiler = (inPath:string, inKey:string, inCheck?:boolean)=>Promise; + const TranspileURL:Transpiler =async(inPath, inKey, inCheck)=> + { + if(inCheck) + { + const cached = Transpiled.get(inKey); + if(cached) + { + return cached; + } + } + let body = await fetch(inPath); + let text = await body.text(); + return Transpile(text, inKey); + }; + + const Sockets:Set = new Set(); + const SocketsBroadcast =(inData:string)=>{ for (const socket of Sockets){ socket.send(inData); } } + const SocketsHandler = inDevMode ? (_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 false; + } + : + ()=>false; + + const watcher =async()=> + { + const FilesChanged:Map = new Map(); + const ProcessFiles =debounce(async()=> + { + for await (const [path, action] of FilesChanged) + { + const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); + if(action != "remove") + { + await TranspileURL(Path.Active+key, key, false); + SocketsBroadcast(key); + } + else + { + Transpiled.delete(key); + } + } + FilesChanged.clear(); + }, 1000); + for await (const event of Deno.watchFs(Deno.cwd())) + { + event.paths.forEach( path => + { + if(Transpileable(path)) + { + FilesChanged.set(path, event.kind); + } + }); + if(FilesChanged.size) + { + ProcessFiles(); + } } } - if(inFilePath[dotIndex+2] == "s") + if(inDevMode) { - const first = inFilePath[dotIndex+1]; - return (first == "t" || first == "j"); + watcher().then(()=>{console.log("done watching");}); } - return false; -}; -const Transpile =async(inCode:string, inKey:string):Promise=> -{ - const transpile = await ESBuild.transform(inCode, { loader: "tsx", sourcemap:"inline", minify:true, sourcefile:inKey}); - - Transpiled.set(inKey, transpile.code); - return transpile.code; -}; -type Transpiler = (inPath:string, inKey:string, inCheck?:boolean)=>Promise; -const TranspileURL:Transpiler =async(inPath, inKey, inCheck)=> -{ - if(inCheck) - { - const cached = Transpiled.get(inKey); - if(cached) - { - return cached; - } - } - let body = await fetch(inPath); - let text = await body.text(); - return Transpile(text, inKey); -}; - - -const LibPath = "lib"; + return {TranspileURL, Transpileable, SocketsHandler}; +} +/** + * Extract all configuration info form a project's deno.jsonc file + * @param inDevMode When true, proxies react to an HMR-enabled version + * @param inLibPath + * @returns + */ async function Configure(inDevMode:boolean, inLibPath:string) { type ImportMap = {imports?:Record, importMap?:string}; @@ -194,14 +266,23 @@ async function Configure(inDevMode:boolean, inLibPath:string) return output; } -const {Imports, App, TwindInst, Error} = await Configure(true, LibPath); +let DevMode = true; +let hosted = import.meta.resolve("./"); +const Path = { + Hosted: hosted.substring(0, hosted.length-1), + Active: `file://${Deno.cwd().replaceAll("\\", "/")}`, + LibDir: "lib" +}; +console.log(Path); +console.log(`Dev Mode: ${DevMode}`); +const {Transpileable, TranspileURL, SocketsHandler} = Transpiler(DevMode); +const {Imports, App, TwindInst, Error} = await Configure(DevMode, Path.LibDir); if(Error) { console.log(Error); } - -if(App && TwindInst) +else if(App && TwindInst) { Deno.serve({ port: Deno.args[0]||3000 }, async(_req:Request) => { @@ -209,35 +290,9 @@ if(App && TwindInst) const pathParts = url.pathname.substring(1, url.pathname.endsWith("/") ? url.pathname.length-1 : url.pathname.length).split("/"); const pathLast = pathParts.at(-1); const pathExt:string|undefined = pathLast?.split(".")[1]; - - console.log(pathParts, pathLast, pathExt); - - console.log(`Request for "${url.pathname}"...`); - - if(_req.headers.get("upgrade") == "websocket") - { - try - { - const { response, socket } = Deno.upgradeWebSocket(_req); - socket.onopen = () => - { - Sockets.add(socket); - console.log("Overwatch: Socket created"); - }; - socket.onclose = () => - { - Sockets.delete(socket); - console.log("Overwatch: Socket deleted"); - }; - socket.onmessage = (e) => {}; - socket.onerror = (e) => console.log("Overwatch: Socket errored:", e); - return response; - } - catch(e) - { - // - } - } + + const resp = SocketsHandler(_req); + if(resp){ return resp; } try { @@ -245,7 +300,7 @@ if(App && TwindInst) let type = `text/html`; let body:BodyInit = ``; - const isLib = url.pathname.startsWith(`/${LibPath}/`); + const isLib = url.pathname.startsWith(`/${Path.LibDir}/`); if(Transpileable(url.pathname)) { @@ -261,7 +316,7 @@ if(App && TwindInst) for( const key in imp ) { members.push(key); } body = ` - import {FileListen} from "/${LibPath}/hmr.tsx"; + import {FileListen} from "/${Path.LibDir}/hmr.tsx"; import * as Import from "${url.pathname}?reload=0"; ${ members.map(m=>`let proxy_${m} = Import.${m}; export { proxy_${m} as ${m} }; @@ -282,7 +337,7 @@ if(App && TwindInst) else if( pathExt ) { type = MIME.typeByExtension(pathExt) || "text/html"; - const _fetch = await fetch((isLib ? Path.Hosted : Path.Active)+url.pathname); + const _fetch = await fetch((Path.Active)+url.pathname); body = await _fetch.text(); } else @@ -349,44 +404,4 @@ if(App && TwindInst) 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()=> - { - console.log("Processing Files...", FilesChanged); - for await (const [path, action] of FilesChanged) - { - const key = path.substring(Deno.cwd().length).replaceAll("\\", "/"); - console.log(key, action); - - if(action != "remove") - { - await TranspileURL(Path.Active+key, key, false); - console.log(` ...cached "${key}"`); - SocketsBroadcast(key); - } - else - { - Transpiled.delete(key); - } - } - FilesChanged.clear(); - }, 1000); - for await (const event of Deno.watchFs(Deno.cwd())) - { - event.paths.forEach( path => - { - if(Transpileable(path)) - { - FilesChanged.set(path, event.kind); - } - }); - if(FilesChanged.size) - { - ProcessFiles(); - } - } } \ No newline at end of file