modes #23
| @ -1,5 +1,3 @@ | |||||||
| import TWPreTail from "https://esm.sh/v115/@twind/preset-tailwind@1.1.4/es2022/preset-tailwind.mjs"; |  | ||||||
| import TWPreAuto from "https://esm.sh/v115/@twind/preset-autoprefix@1.0.7/es2022/preset-autoprefix.mjs"; |  | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import * as Iso from "@eno/iso"; | import * as Iso from "@eno/iso"; | ||||||
| 
 | 
 | ||||||
| @ -36,8 +34,3 @@ export default ()=> | |||||||
|         </Iso.Switch> |         </Iso.Switch> | ||||||
|     </div>; |     </div>; | ||||||
| }; | }; | ||||||
| 
 |  | ||||||
| export const CSS = { |  | ||||||
|     presets: [TWPreTail(), TWPreAuto()], |  | ||||||
|     hash:false |  | ||||||
| }; |  | ||||||
|  | |||||||
| @ -2,13 +2,13 @@ | |||||||
|     "compilerOptions": {"lib": ["deno.window", "dom"]}, |     "compilerOptions": {"lib": ["deno.window", "dom"]}, | ||||||
|     "imports": |     "imports": | ||||||
|     { |     { | ||||||
|         "react": "https://esm.sh/preact@10.13.2/compat", |         "react": "https://esm.sh/stable/preact@10.13.2/compat", | ||||||
|  |         "preact": "https://esm.sh/stable/preact@10.13.2/", | ||||||
|         "@deep/": "./deep/", |         "@deep/": "./deep/", | ||||||
|         "@eno/app": "./app.tsx", |         "@eno/app": "./app.tsx", | ||||||
|         "@eno/iso": "http://localhost:4507/lib/iso.tsx" |         "@eno/iso": "http://localhost:4507/lib/iso.tsx" | ||||||
|     }, |     }, | ||||||
|     "tasks": { |     "tasks": { | ||||||
|         "host": "deno run -A --unstable https://deno.land/std@0.181.0/http/file_server.ts", |         "dev": "deno run -A --unstable --reload=http://localhost:4507/ --no-lock app.tsx --dev" | ||||||
|         "dev": "deno run -A --unstable --reload=http://localhost:4507/ --no-lock --config=deno.json 'http://localhost:4507/server.tsx?reload=1'" |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										40
									
								
								lib/iso.tsx
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								lib/iso.tsx
									
									
									
									
									
								
							| @ -1,5 +1,17 @@ | |||||||
|  | import TWPreTail from "https://esm.sh/v115/@twind/preset-tailwind@1.1.4/es2022/preset-tailwind.mjs"; | ||||||
|  | import TWPreAuto from "https://esm.sh/v115/@twind/preset-autoprefix@1.0.7/es2022/preset-autoprefix.mjs"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
| 
 | 
 | ||||||
|  | export const CSS = { | ||||||
|  |     presets: [TWPreTail(), TWPreAuto()], | ||||||
|  |     hash:false | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | if(!window.innerWidth) | ||||||
|  | { | ||||||
|  |     import(import.meta.resolve("../../server.tsx")).then(()=>{console.log("...imported!");}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type MetasInputs = { [Property in MetaKeys]?: string }; | type MetasInputs = { [Property in MetaKeys]?: string }; | ||||||
| type MetasModeArgs = {concatListed?:boolean; dropUnlisted?:boolean}; | type MetasModeArgs = {concatListed?:boolean; dropUnlisted?:boolean}; | ||||||
| type MetasStackItem = MetasModeArgs&MetasInputs&{id:string, depth:number} | type MetasStackItem = MetasModeArgs&MetasInputs&{id:string, depth:number} | ||||||
| @ -80,34 +92,6 @@ export const Meta = | |||||||
|             }); |             }); | ||||||
|         }, Object.keys(props).map( (key) => props[key as MetaKeys] )); |         }, Object.keys(props).map( (key) => props[key as MetaKeys] )); | ||||||
| 
 | 
 | ||||||
|         /* |  | ||||||
|         React.useEffect(()=>{ |  | ||||||
| 
 |  | ||||||
|             metasSet((m)=> |  | ||||||
|             { |  | ||||||
|                 const metas = {...m}; |  | ||||||
| 
 |  | ||||||
|                 const    additive = concatListed ? (key:MetaKeys, value:string)=> metas[key] += value : (key:MetaKeys, value:string)=> metas[key] = value; |  | ||||||
|                 const subtractive = dropUnlisted ? (key:MetaKeys)=> metas[key] = "" : (key:MetaKeys)=> {}; |  | ||||||
|              |  | ||||||
|                 Object.keys(metas).forEach((key)=>{ |  | ||||||
|                     const metaKey = key as MetaKeys; |  | ||||||
|                     const propValue = props[metaKey]||""; |  | ||||||
|                     propValue ? additive(metaKey, propValue) : subtractive(metaKey); |  | ||||||
|                 }); |  | ||||||
| 
 |  | ||||||
|                 Meta.Meta = metas; |  | ||||||
|                 console.log(Meta.Meta); |  | ||||||
| 
 |  | ||||||
|                 if(window.innerWidth) |  | ||||||
|                 { |  | ||||||
|                     document.title = metas.title; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 return metas; |  | ||||||
|             }); |  | ||||||
|         }, [props]); |  | ||||||
|         */ |  | ||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										466
									
								
								server.tsx
									
									
									
									
									
								
							
							
						
						
									
										466
									
								
								server.tsx
									
									
									
									
									
								
							| @ -1,22 +1,25 @@ | |||||||
| import * as ESBuild from 'https://deno.land/x/esbuild@v0.14.45/mod.js'; | import * as ESBuild from 'https://deno.land/x/esbuild@v0.17.4/mod.js'; | ||||||
| import * as MIME from "https://deno.land/std@0.180.0/media_types/mod.ts"; | 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"; | import { debounce } from "https://deno.land/std@0.151.0/async/debounce.ts"; | ||||||
|  | import { parse as JSONC} from "https://deno.land/std@0.185.0/jsonc/mod.ts"; | ||||||
| import SSR from "https://esm.sh/v113/preact-render-to-string@6.0.2"; | import SSR from "https://esm.sh/v113/preact-render-to-string@6.0.2"; | ||||||
| import Prepass from "https://esm.sh/preact-ssr-prepass@1.2.0"; | import Prepass from "https://esm.sh/preact-ssr-prepass@1.2.0"; | ||||||
| import * as Twind from "https://esm.sh/@twind/core@1.1.3"; | import * as Twind from "https://esm.sh/@twind/core@1.1.3"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import * as Iso from "@eno/iso"; | import * as Iso from "@eno/iso"; | ||||||
| 
 | 
 | ||||||
| let hosted = import.meta.resolve("./"); | Deno.env.set("initialized", "true"); | ||||||
| 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) | ||||||
| { | { | ||||||
|  |     const Transpiled = new Map(); | ||||||
|  |     const Transpileable =(inFilePath:string):boolean=> | ||||||
|  |     { | ||||||
|         let dotIndex = inFilePath.length-4; |         let dotIndex = inFilePath.length-4; | ||||||
|         if(inFilePath[dotIndex] !== ".") |         if(inFilePath[dotIndex] !== ".") | ||||||
|         { |         { | ||||||
| @ -33,17 +36,21 @@ const Transpileable =(inFilePath:string):boolean=> | |||||||
|         } |         } | ||||||
|      |      | ||||||
|         return false; |         return false; | ||||||
| }; |     }; | ||||||
| const Transpile =async(inCode:string, inKey:string):Promise<string>=> |     const Transpile =async(inCode:string, inKey:string):Promise<string>=> | ||||||
| { |     { | ||||||
|     const transpile = await ESBuild.transform(inCode, { loader: "tsx", sourcemap:"inline", minify:true, sourcefile:inKey}); |         const transpile = await ESBuild.transform(inCode, inDevMode ? { loader: "tsx", sourcemap:"inline", minify: false, sourcefile:inKey} : | ||||||
|  |         {  loader: "tsx", | ||||||
|  |         minify:true, | ||||||
|  |         jsx:"automatic", | ||||||
|  |         jsxImportSource:"https://esm.sh/preact@10.13.2"}); | ||||||
|      |      | ||||||
|         Transpiled.set(inKey, transpile.code); |         Transpiled.set(inKey, transpile.code); | ||||||
|         return transpile.code; |         return transpile.code; | ||||||
| }; |     }; | ||||||
| type Transpiler = (inPath:string, inKey:string, inCheck?:boolean)=>Promise<string>; |     type Transpiler = (inPath:string, inKey:string, inCheck?:boolean)=>Promise<string>; | ||||||
| const TranspileURL:Transpiler =async(inPath, inKey, inCheck)=> |     const TranspileURL:Transpiler =async(inPath, inKey, inCheck)=> | ||||||
| { |     { | ||||||
|         if(inCheck) |         if(inCheck) | ||||||
|         { |         { | ||||||
|             const cached = Transpiled.get(inKey); |             const cached = Transpiled.get(inKey); | ||||||
| @ -55,118 +62,21 @@ const TranspileURL:Transpiler =async(inPath, inKey, inCheck)=> | |||||||
|         let body = await fetch(inPath); |         let body = await fetch(inPath); | ||||||
|         let text = await body.text(); |         let text = await body.text(); | ||||||
|         return Transpile(text, inKey); |         return Transpile(text, inKey); | ||||||
| }; |     }; | ||||||
|      |      | ||||||
| 
 |     const Sockets:Set<WebSocket> = new Set(); | ||||||
| const LibPath = "lib"; |     const SocketsBroadcast =(inData:string)=>{ for (const socket of Sockets){ socket.send(inData); } } | ||||||
| type ImportMap = {imports?:Record<string, string>, importMap?:string}; |     const SocketsHandler = inDevMode ? (_req:Request)=> | ||||||
| let ImportObject:ImportMap = {}; |  | ||||||
| try |  | ||||||
| { |  | ||||||
|     const confDeno = await fetch(`${Path.Active}/deno.json`); |  | ||||||
|     const confDenoParsed:ImportMap = await confDeno.json(); |  | ||||||
|     if(confDenoParsed.importMap) |  | ||||||
|     { |     { | ||||||
|         try |  | ||||||
|         { |  | ||||||
|             const confImports = await Deno.readTextFile(confDenoParsed.importMap); |  | ||||||
|             try |  | ||||||
|             { |  | ||||||
|                 ImportObject = JSON.parse(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}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if(ImportObject.imports) |  | ||||||
|     { |  | ||||||
|         const importReact = ImportObject.imports["react"]; |  | ||||||
|         if(importReact) |  | ||||||
|         { |  | ||||||
|             ImportObject.imports["react-original"] = importReact; |  | ||||||
|             ImportObject.imports["react"] = `/${LibPath}/react.tsx`; |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             console.log(`"imports" configuration does not alias "react"`); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const importApp = ImportObject.imports["@eno/app"]; |  | ||||||
|         if(importApp) |  | ||||||
|         { |  | ||||||
|              |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             console.log(`"imports" configuration does not alias an entry-point component with "@eno/app"`); |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         const importIso = ImportObject.imports["@eno/iso"]; |  | ||||||
|         if(importIso) |  | ||||||
|         { |  | ||||||
|             ImportObject.imports["@eno/iso"] = `/${LibPath}/iso.tsx`; |  | ||||||
|         } |  | ||||||
|         else |  | ||||||
|         { |  | ||||||
| 
 |  | ||||||
|         } |  | ||||||
|         Object.entries(ImportObject.imports).forEach(([key, value])=>{ |  | ||||||
|             if(value.startsWith("./") && ImportObject.imports) |  | ||||||
|             { |  | ||||||
|                 ImportObject.imports[key] = value.substring(1); |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|     else |  | ||||||
|     { |  | ||||||
|         console.log(`No "imports" found in configuration`); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| catch(e) |  | ||||||
| { |  | ||||||
|     console.log(`deno.json not found`); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| Deno.serve({ port: Deno.args[0]||3000 }, async(_req:Request) => |  | ||||||
| { |  | ||||||
|     const url:URL = new URL(_req.url); |  | ||||||
|     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") |         if(_req.headers.get("upgrade") == "websocket") | ||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|               const { response, socket } = Deno.upgradeWebSocket(_req); |               const { response, socket } = Deno.upgradeWebSocket(_req); | ||||||
|           socket.onopen = () => |               socket.onopen = () => Sockets.add(socket); | ||||||
|           { |               socket.onclose = () => Sockets.delete(socket); | ||||||
|               Sockets.add(socket); |  | ||||||
|               console.log("Overwatch: Socket created"); |  | ||||||
|           }; |  | ||||||
|           socket.onclose = () => |  | ||||||
|           { |  | ||||||
|               Sockets.delete(socket); |  | ||||||
|               console.log("Overwatch: Socket deleted"); |  | ||||||
|           }; |  | ||||||
|               socket.onmessage = (e) => {}; |               socket.onmessage = (e) => {}; | ||||||
|           socket.onerror = (e) => console.log("Overwatch: Socket errored:", e); |               socket.onerror = (e) => console.log("Socket errored:", e); | ||||||
|               return response; |               return response; | ||||||
|             } |             } | ||||||
|             catch(e) |             catch(e) | ||||||
| @ -174,6 +84,232 @@ Deno.serve({ port: Deno.args[0]||3000 }, async(_req:Request) => | |||||||
|                 //
 |                 //
 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |     : | ||||||
|  |     ()=>false; | ||||||
|  | 
 | ||||||
|  |     const watcher =async()=> | ||||||
|  |     { | ||||||
|  |         const FilesChanged:Map<string, string> = 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(inDevMode) | ||||||
|  |     { | ||||||
|  |         watcher().then(()=>{console.log("done watching");}); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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<string, string>, importMap?:string}; | ||||||
|  |     const output:{Imports?:ImportMap, App?:React.FunctionComponent, TwindInst?:Twind.Twind, Error?:string} = {}; | ||||||
|  |     let ImportObject:ImportMap = {}; | ||||||
|  |     try | ||||||
|  |     { | ||||||
|  |         const confDeno = await fetch(`${Path.Active}/deno.jsonc`); | ||||||
|  |         const confText = await confDeno.text(); | ||||||
|  |         const confDenoParsed = JSONC(confText) as ImportMap; | ||||||
|  |         if(confDenoParsed.importMap) | ||||||
|  |         { | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 const confImports = await Deno.readTextFile(confDenoParsed.importMap); | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     output.Imports = JSON.parse(confImports); | ||||||
|  |                 } | ||||||
|  |                 catch(e) | ||||||
|  |                 { | ||||||
|  |                     output.Error = `"importMap" "${confDenoParsed.importMap}" contains invalid JSON`; | ||||||
|  |                     return output; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch(e) | ||||||
|  |             { | ||||||
|  |                 output.Error = `"importMap" "${confDenoParsed.importMap}" cannot be found`; | ||||||
|  |                 return output; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else if(confDenoParsed.imports) | ||||||
|  |         { | ||||||
|  |             output.Imports  = {imports:confDenoParsed.imports}; | ||||||
|  |         } | ||||||
|  |      | ||||||
|  |         if(output.Imports?.imports) | ||||||
|  |         { | ||||||
|  |             if(inDevMode) | ||||||
|  |             { | ||||||
|  |                 const importReact = output.Imports.imports["react"]; | ||||||
|  |                 if(importReact) | ||||||
|  |                 { | ||||||
|  |                     output.Imports.imports["react-original"] = importReact; | ||||||
|  |                     output.Imports.imports["react"] = `/${inLibPath}/react.tsx`; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     output.Error = `"imports" configuration does not alias "react"`; | ||||||
|  |                     return output; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |      | ||||||
|  |             const importIso = output.Imports.imports["@eno/iso"]; | ||||||
|  |             if(importIso) | ||||||
|  |             { | ||||||
|  |                 output.Imports.imports["@eno/iso"] = `/${inLibPath}/iso.tsx`; | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 output.Error = `"imports" configuration does not alias "@eno/iso"`; | ||||||
|  |                 return output; | ||||||
|  |             } | ||||||
|  |      | ||||||
|  |             const importApp = output.Imports.imports["@eno/app"]; | ||||||
|  |             if(importApp) | ||||||
|  |             { | ||||||
|  |                 let appImport  | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     appImport = await import(Path.Active+importApp); | ||||||
|  |                 } | ||||||
|  |                 catch(e) | ||||||
|  |                 { | ||||||
|  |                     output.Error = `"@eno/app" entry-point (${importApp}) file not found`; | ||||||
|  |                     return output; | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 if(typeof appImport.default == "function" ) | ||||||
|  |                 { | ||||||
|  |                     output.App = appImport.default; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     output.Error = `"@eno/app" entry-point (${importApp}) needs to export a default function to use as the app root.`; | ||||||
|  |                     return output; | ||||||
|  |                 } | ||||||
|  |      | ||||||
|  |                 let twindConfig = Iso.CSS; | ||||||
|  |                 if(typeof appImport.CSS == "object") | ||||||
|  |                 { | ||||||
|  |                     twindConfig = {...twindConfig, ...appImport.CSS}; | ||||||
|  |                 } | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     // @ts-ignore
 | ||||||
|  |                     output.TwindInst = Twind.install(twindConfig); | ||||||
|  |                 } | ||||||
|  |                 catch(e) | ||||||
|  |                 { | ||||||
|  |                     output.Error = `CSS configuration is malformed`; | ||||||
|  |                     return output; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 output.Error = `"imports" configuration does not alias an entry-point file as "@eno/app"`; | ||||||
|  |                 return output; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |      | ||||||
|  |             Object.entries(output.Imports.imports).forEach(([key, value])=>{ | ||||||
|  |                 if(value.startsWith("./") && output.Imports?.imports) | ||||||
|  |                 { | ||||||
|  |                     output.Imports.imports[key] = value.substring(1); | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             output.Error = `No "imports" found in configuration`; | ||||||
|  |             return output; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     catch(e) | ||||||
|  |     { | ||||||
|  |         output.Error = `error during configuration: ${e}`; | ||||||
|  |         return output; | ||||||
|  |     } | ||||||
|  |     return output; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const Flags:Record<string, string|boolean> = {}; | ||||||
|  | Deno.args.forEach(arg=> | ||||||
|  | { | ||||||
|  |     if(arg.startsWith("--")) | ||||||
|  |     { | ||||||
|  |         const kvp = arg.substring(2).split("="); | ||||||
|  |         Flags[kvp[0]] = kvp[1] ? kvp[1] : true;  | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | let DevMode = Flags.dev ? true : false; | ||||||
|  | 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}`); | ||||||
|  | console.log(`Args seen: `, Flags); | ||||||
|  | 
 | ||||||
|  | const {Transpileable, TranspileURL, SocketsHandler} = Transpiler(DevMode); | ||||||
|  | const {Imports, App, TwindInst, Error} = await Configure(DevMode, Path.LibDir); | ||||||
|  | if(Error) | ||||||
|  | { | ||||||
|  |     console.log(Error); | ||||||
|  | } | ||||||
|  | else if(App && TwindInst) | ||||||
|  | { | ||||||
|  |     Deno.serve({ port: Flags?.port||3000 }, async(_req:Request) => | ||||||
|  |     { | ||||||
|  |         const url:URL = new URL(_req.url); | ||||||
|  |         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]; | ||||||
|  |          | ||||||
|  |         const resp = SocketsHandler(_req); | ||||||
|  |         if(resp){ return resp; } | ||||||
|      |      | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
| @ -181,7 +317,7 @@ Deno.serve({ port: Deno.args[0]||3000 }, async(_req:Request) => | |||||||
|             let type = `text/html`; |             let type = `text/html`; | ||||||
|             let body:BodyInit = ``; |             let body:BodyInit = ``; | ||||||
|      |      | ||||||
|         const isLib = url.pathname.startsWith(`/${LibPath}/`); |             const isLib = url.pathname.startsWith(`/${Path.LibDir}/`); | ||||||
|      |      | ||||||
|             if(Transpileable(url.pathname)) |             if(Transpileable(url.pathname)) | ||||||
|             { |             { | ||||||
| @ -190,23 +326,23 @@ Deno.serve({ port: Deno.args[0]||3000 }, async(_req:Request) => | |||||||
|                 { |                 { | ||||||
|                     body = await TranspileURL(Path.Hosted+url.pathname, url.pathname, true); |                     body = await TranspileURL(Path.Hosted+url.pathname, url.pathname, true); | ||||||
|                 } |                 } | ||||||
|             else if(!url.searchParams.get("reload")) |                 else if(DevMode && !url.searchParams.get("reload")) | ||||||
|                 { |                 { | ||||||
|                     const imp = await import(Path.Active+url.pathname); |                     const imp = await import(Path.Active+url.pathname); | ||||||
|                     const members = []; |                     const members = []; | ||||||
|                     for( const key in imp ) { members.push(key); } |                     for( const key in imp ) { members.push(key); } | ||||||
|                     body = |                     body = | ||||||
| ` |     ` | ||||||
| import {FileListen} from "/${LibPath}/hmr.tsx"; |     import {FileListen} from "/${Path.LibDir}/hmr.tsx"; | ||||||
| import * as Import from "${url.pathname}?reload=0"; |     import * as Import from "${url.pathname}?reload=0"; | ||||||
| ${ members.map(m=>`let proxy_${m} = Import.${m};
 |     ${ members.map(m=>`let proxy_${m} = Import.${m};
 | ||||||
| export { proxy_${m} as ${m} }; |     export { proxy_${m} as ${m} }; | ||||||
| `).join(" ") }
 |     `).join(" ") }
 | ||||||
| const reloadHandler = (updatedModule)=> |     const reloadHandler = (updatedModule)=> | ||||||
| { |     { | ||||||
|         ${ members.map(m=>`proxy_${m} = updatedModule.${m};`).join("\n") } |         ${ members.map(m=>`proxy_${m} = updatedModule.${m};`).join("\n") } | ||||||
| }; |     }; | ||||||
| FileListen("${url.pathname}", reloadHandler);`;
 |     FileListen("${url.pathname}", reloadHandler);`;
 | ||||||
|      |      | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
| @ -218,7 +354,7 @@ FileListen("${url.pathname}", reloadHandler);`; | |||||||
|             else if( pathExt ) |             else if( pathExt ) | ||||||
|             { |             { | ||||||
|                 type = MIME.typeByExtension(pathExt) || "text/html"; |                 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(); |                 body = await _fetch.text(); | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
| @ -226,7 +362,7 @@ FileListen("${url.pathname}", reloadHandler);`; | |||||||
|                 Iso.Fetch.ServerBlocking = []; |                 Iso.Fetch.ServerBlocking = []; | ||||||
|                 Iso.Fetch.ServerTouched = new Set(); |                 Iso.Fetch.ServerTouched = new Set(); | ||||||
|                 Iso.Fetch.ServerRemove = new Set(); |                 Iso.Fetch.ServerRemove = new Set(); | ||||||
|             let app = <Iso.Router.Provider url={url}><Iso.Meta.Provider><App/></Iso.Meta.Provider></Iso.Router.Provider>; |                 let app = <Iso.Router.Provider url={url}><App/></Iso.Router.Provider>; | ||||||
|                 await Prepass(app) |                 await Prepass(app) | ||||||
|                 let bake = SSR(app); |                 let bake = SSR(app); | ||||||
|                 while(Iso.Fetch.ServerBlocking.length) |                 while(Iso.Fetch.ServerBlocking.length) | ||||||
| @ -235,7 +371,7 @@ FileListen("${url.pathname}", reloadHandler);`; | |||||||
|                     Iso.Fetch.ServerBlocking = []; |                     Iso.Fetch.ServerBlocking = []; | ||||||
|                     // at this point, anything that was requested that was not cached, has now been loaded and cached
 |                     // at this point, anything that was requested that was not cached, has now been loaded and cached
 | ||||||
|                     // this next render will use cached resources. using a cached resource (if its "Seed" is true) adds it to the "touched" set.
 |                     // this next render will use cached resources. using a cached resource (if its "Seed" is true) adds it to the "touched" set.
 | ||||||
|                 app = <Iso.Router.Provider url={url}><Iso.Meta.Provider><App/></Iso.Meta.Provider></Iso.Router.Provider>; |                     app = <Iso.Router.Provider url={url}><App/></Iso.Router.Provider>; | ||||||
|                     await Prepass(app) |                     await Prepass(app) | ||||||
|                     bake = SSR(app); |                     bake = SSR(app); | ||||||
|                 } |                 } | ||||||
| @ -252,25 +388,25 @@ FileListen("${url.pathname}", reloadHandler);`; | |||||||
|      |      | ||||||
|                 type = `text/html`; |                 type = `text/html`; | ||||||
|                 body = |                 body = | ||||||
| `<!doctype html>
 |     `<!doctype html>
 | ||||||
| <html lang="en"> |     <html lang="en"> | ||||||
|         <head> |         <head> | ||||||
|             <title>${Iso.Meta.Meta.title}</title> |             <title>${Iso.Meta.Meta.title}</title> | ||||||
|             <meta name="viewport" content="width=device-width, initial-scale=1.0"> |             <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|             <meta charset="utf-8"/> |             <meta charset="utf-8"/> | ||||||
|             <style data-twind>${results.css}</style> |             <style data-twind>${results.css}</style> | ||||||
|         <script type="importmap">${JSON.stringify(ImportObject)}</script> |             <script type="importmap">${JSON.stringify(Imports)}</script> | ||||||
|         </head> |         </head> | ||||||
|         <body> |         <body> | ||||||
|             <div id="app">${results.html}</div> |             <div id="app">${results.html}</div> | ||||||
|             <script type="module"> |             <script type="module"> | ||||||
|                 import {hydrate, createElement as H} from "react"; |                 import {hydrate, createElement as H} from "react"; | ||||||
|                 import * as Twind from "https://esm.sh/v115/@twind/core@1.1.3/es2022/core.mjs"; |                 import * as Twind from "https://esm.sh/v115/@twind/core@1.1.3/es2022/core.mjs"; | ||||||
|             import {default as App, CSS} from "@eno/app"; |                 import * as App from "@eno/app"; | ||||||
|             import {Router, Fetch, Meta} from "@eno/iso"; |                 import {Router, Fetch, CSS, Meta} from "@eno/iso"; | ||||||
|             Twind.install(CSS); |                 Twind.install(App.CSS ? {...CSS, ...App.CSS} : CSS); | ||||||
|                 Fetch.Seed(${JSON.stringify(seed)}); |                 Fetch.Seed(${JSON.stringify(seed)}); | ||||||
|             const hmrWrap = H( ()=>H(App) ); |                 const hmrWrap = H( ()=>H(App.default) ); | ||||||
|                 hydrate( |                 hydrate( | ||||||
|                     H(Router.Provider, null, |                     H(Router.Provider, null, | ||||||
|                         H(Meta.Provider, null, hmrWrap) |                         H(Meta.Provider, null, hmrWrap) | ||||||
| @ -279,7 +415,7 @@ FileListen("${url.pathname}", reloadHandler);`; | |||||||
|                 ); |                 ); | ||||||
|             </script> |             </script> | ||||||
|         </body> |         </body> | ||||||
| </html>`;
 |     </html>`;
 | ||||||
|             } |             } | ||||||
|      |      | ||||||
|             return new Response(body, {headers:{"content-type":type as string, "Access-Control-Allow-Origin":"*", charset:"utf-8"}}); |             return new Response(body, {headers:{"content-type":type as string, "Access-Control-Allow-Origin":"*", charset:"utf-8"}}); | ||||||
| @ -289,47 +425,5 @@ FileListen("${url.pathname}", reloadHandler);`; | |||||||
|             console.log(error); |             console.log(error); | ||||||
|             return new Response(error, {status:404}); |             return new Response(error, {status:404}); | ||||||
|         } |         } | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| import App, {CSS} from "@eno/app"; |  | ||||||
| const TwindInst = Twind.install(CSS); |  | ||||||
| 
 |  | ||||||
| const Sockets:Set<WebSocket> = new Set(); |  | ||||||
| const SocketsBroadcast =(inData:string)=>{ for (const socket of Sockets){ socket.send(inData); } } |  | ||||||
| 
 |  | ||||||
| const FilesChanged:Map<string, string> = 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(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user