diff --git a/hmr/hmr-listen.tsx b/hmr/hmr-listen.tsx index 6b46056..2495595 100644 --- a/hmr/hmr-listen.tsx +++ b/hmr/hmr-listen.tsx @@ -22,7 +22,9 @@ function connect() { }); socket.addEventListener("message", async (ev) => { try { - const reImport = await import(location.origin + ev.data + "?reload=" + Math.random()); + const reloadPath = location.origin + ev.data + "?reload=" + Math.random(); + console.log("realod called", ev.data, reloadPath); + const reImport = await import(reloadPath); FileListeners.get(ev.data)?.forEach(h => h(reImport)); HMR.update(); } catch (e) { diff --git a/hmr/hmr-static.tsx b/hmr/hmr-static.tsx index a9c0bb5..e08ecfa 100644 --- a/hmr/hmr-static.tsx +++ b/hmr/hmr-static.tsx @@ -114,11 +114,11 @@ export const ModuleShape =(inText:string)=> return[local, foreign] as [local:string[], foreign:string[]]; }; -export const ModuleProxy =(inText:string, inPath:string)=> +export const ModuleProxy =(inText:string, inPath:string, reactProxy:string)=> { const [local, foreign] = ModuleShape(inText); return ` -import {FileListen} from "/^/hmr/hmr-listen.tsx"; +import {FileListen} from "${reactProxy}"; import * as Import from "${inPath}?reload=${new Date().getTime()}"; ${ local.map(m=>`let proxy_${m} = Import.${m}; export { proxy_${m} as ${m} };`).join("\n") } FileListen("${inPath}", (updatedModule)=> diff --git a/server.ts b/server.ts index 4905aec..58fbd00 100644 --- a/server.ts +++ b/server.ts @@ -1,8 +1,8 @@ import { contentType } from "jsr:@std/media-types"; import { ModuleProxy } from "./hmr/hmr-static.tsx"; -const keyBundle = encodeURI(">"); -const keyAdjacent = encodeURI("^"); +const keyBundle = "=>"; +const keyTranspile = "->"; const keyReload = "reload"; const keysExtension = ["ts", "tsx"]; const extractExtension =(path:string)=> @@ -28,12 +28,17 @@ const bakeConfigPackage:Deno.bundle.Options = const bakeConfigLocal:Deno.bundle.Options = {...bakeConfigPackage, sourcemap:"inline", inlineImports:false }; type FullBakeConfig = { - bundle: Deno.bundle.Options, - + path:string, + key?:string, + bundle?:boolean }; -async function BakeForce(prefix:string, path:string, key:string, type?:"package") +async function BakeForce(options:FullBakeConfig) { + const path = options.path; + const key = options.key || path; + const type = options.bundle; + // If already baking, return the in-flight promise. (Caller may also call Bake Check which handles this.) if (BakeCache[key] && typeof (BakeCache[key] as any)?.then === "function") { @@ -41,8 +46,8 @@ async function BakeForce(prefix:string, path:string, key:string, type?:"package" } // Create a fresh config per bake to avoid shared mutation. - const config = {...(type ? bakeConfigPackage : bakeConfigLocal), entrypoints:[prefix + path]}; - console.log("baking", config.entrypoints, "as", [prefix, path]); + const config = {...(type ? bakeConfigPackage : bakeConfigLocal), entrypoints:[path]}; + console.log("baking", config.entrypoints, "as", key); // store the in-flight promise immediately so concurrent callers reuse it const inflight = (async () => @@ -53,7 +58,8 @@ async function BakeForce(prefix:string, path:string, key:string, type?:"package" if (result.outputFiles) { const body = result.outputFiles.map(file=>file.text()).join("\n"); - const save:CachedTranspile = [body, type ? "" : ModuleProxy(body, path)]; + const listenerImport = `/${keyTranspile}/${import.meta.resolve("./hmr/hmr-listen.tsx")}`; + const save:CachedTranspile = [body, type ? "" : ModuleProxy(body, path, listenerImport)]; BakeCache[key] = save; // replace promise with resolved value return save; } @@ -70,12 +76,14 @@ async function BakeForce(prefix:string, path:string, key:string, type?:"package" BakeCache[key] = inflight; return await inflight; }; -async function BakeCheck(prefix:string, path:string, key:string, type?:"package") +async function BakeCheck(options:FullBakeConfig) { - const lookup = BakeCache[path]; + const key = options.key || options.path; + + const lookup = BakeCache[key]; if(!lookup) { - return await BakeForce(prefix, path, type); + return await BakeForce(options); } // if an in-flight promise is stored, await it if (typeof (lookup as any)?.then === "function") @@ -94,13 +102,14 @@ for(const key in denoBody.imports) const value = denoBody.imports[key]; if(value.startsWith("npm:")) { - denoBody.imports[key] = "/>/"+value; + denoBody.imports[key] = `/${keyBundle}/${value}`; } } -const react = denoBody.compilerOptions.jsxImportSource; -denoBody.imports["react-original"] = denoBody.imports[react]; -denoBody.imports[react] = "/^/hmr/hmr-react.tsx"; -denoBody.imports[react+"/jsx-runtime"] = "/^/hmr/hmr-react.tsx"; +const reactKey = denoBody.compilerOptions.jsxImportSource; +denoBody.imports["react-original"] = denoBody.imports[reactKey]; +const reactValueModified = `/${keyTranspile}/${import.meta.resolve("./hmr/hmr-react.tsx")}`; +denoBody.imports[reactKey] = reactValueModified +denoBody.imports[reactKey+"/jsx-runtime"] = reactValueModified; /* @@ -152,7 +161,7 @@ Deno.serve(async(req:Request)=> } const url = new URL(req.url); - const parts = url.pathname.split("/").filter(part=>part); + const parts = url.pathname.split("/").filter(part=>part).map(part=>decodeURIComponent(part)); // if there are no path segments, serve index immediately (avoid calling extractExtension on undefined) if(parts.length === 0) @@ -164,20 +173,23 @@ Deno.serve(async(req:Request)=> if(parts[0] == keyBundle) { - const proxiedPath = parts.slice(1).join("/"); - const transpiled = await BakeCheck(proxiedPath, "package"); + console.log("BUNDLE", parts); + const transpiled = await BakeCheck({ + path: parts.slice(1).join("/"), + bundle:true + }); return JSResponse(transpiled[0]); } - if(parts[0] == keyAdjacent) + if(parts[0] == keyTranspile) { - const proxiedPath = RootSiblings + "/" + parts.slice(1).join("/"); - const transpiled = await BakeCheck(proxiedPath); + console.log("TRANSPILE", parts); + const transpiled = await BakeCheck({path:parts.slice(1).join("/"), bundle:false}); return JSResponse(transpiled[0]); } if(keysExtension.includes(extension)) { - const proxiedPath = parts.join("/"); - const transpiled = await BakeCheck(RootRunning, proxiedPath, proxiedPath+url.search); + console.log("REGULAR", parts); + const transpiled = await BakeCheck({path:"."+url.pathname, bundle:false}); //return JSResponse(transpiled[0]); return JSResponse(transpiled[url.searchParams.has(keyReload) ? 0 : 1]); } @@ -229,15 +241,17 @@ const Watcher =async()=> { const key = path.substring(cutOff).replaceAll("\\", "/"); + const keyAbs = "/"+key; + const keyRel = "./"+key; console.log("File change", path, key); if(action != "remove") { - await BakeForce(RootRunning, key, key); - SocketsSend(key); + await BakeForce({path:keyRel, bundle:false}); + SocketsSend(keyRel); } else { - delete BakeCache[key]; + delete BakeCache[keyRel]; } } }