diff --git a/deno.json b/deno.json index 14eb980..1cd8ff9 100644 --- a/deno.json +++ b/deno.json @@ -4,9 +4,8 @@ ]}, "imports": { "react": "https://esm.sh/preact@10.13.2/compat", - + "preact": "https://esm.sh/preact@10.13.2/compat", "react-original": "https://esm.sh/preact@10.13.2/compat", - "@eno/app": "./example/app.tsx", "@eno/iso": "./lib/iso.tsx" }, "tasks": diff --git a/deno.lock b/deno.lock deleted file mode 100644 index b8e174d..0000000 --- a/deno.lock +++ /dev/null @@ -1,16 +0,0 @@ -{ - "version": "2", - "remote": { - "http://localhost:4507/lib/iso.tsx": "edf1cf4c539900040c75824eac8472e0ec43098b13af4049bd79ddbd76a5346d", - "https://esm.sh/preact@10.13.2/compat": "1cf68e0c8c6c84b60d42f30665403b67229c16ff5206824709b19df60ba9cdc3", - "https://esm.sh/stable/preact@10.13.2/deno/compat.js": "3151a948abd84aa75dfc9e57733da7e1a45fff7a25de58c7b6025b923874b508", - "https://esm.sh/stable/preact@10.13.2/deno/hooks.js": "c7a8e703bcbc6a05949f329b618c33d5d1ea5fee113ddcea44ff0f527af8556f", - "https://esm.sh/stable/preact@10.13.2/deno/preact.mjs": "365fab897381f4f403f859c5d12939084560545567108cc90dae901bbe892578", - "https://esm.sh/v116/preact@10.13.2/compat/src/index.d.ts": "d02f015638a40e32649151e011cfda7b520d66f7fbd3c12a28fa03de2a5e1421", - "https://esm.sh/v116/preact@10.13.2/compat/src/suspense-list.d.ts": "b8e274324392157ce476ef3a48ae215c9f7003b08525d140645f19eab20d1948", - "https://esm.sh/v116/preact@10.13.2/compat/src/suspense.d.ts": "81f5266e0977a94347505d11b8103024211f2b4f3b2eb2aa276a10d8fd169e65", - "https://esm.sh/v116/preact@10.13.2/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3", - "https://esm.sh/v116/preact@10.13.2/src/index.d.ts": "65398710de6aa0a07412da79784e05e6e96763f51c7c91b77344d2d0af06385c", - "https://esm.sh/v116/preact@10.13.2/src/jsx.d.ts": "9ac9b82c199fa7b04748807d750eba1a106c0be52041b8617416f88d6fc0a257" - } -} diff --git a/example/app.tsx b/example/app.tsx index 8a7cd9e..da58b1d 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -3,34 +3,36 @@ import * as Iso from "@eno/iso"; const Comp = React.lazy(()=>import("./deep/component.tsx")); -export default ()=> -{ - return
- - - -

Title!!

-

suspended:

- Loading!
}> - - - - - - - <> - - About us! - - - sorry no page - - - lol/idk -

404!

-
- ; -}; \ No newline at end of file +Iso.Boot( + ()=> + { + return
+ + + +

Title!!!!!!

+

suspended:

+ Loading!
}> + + + + + + + <> + + About us! + + + sorry no page + + + lol/idk +

404!

+
+ ; + } +); \ No newline at end of file diff --git a/example/deno.jsonc b/example/deno.jsonc index 2c2f6e0..521187d 100644 --- a/example/deno.jsonc +++ b/example/deno.jsonc @@ -5,10 +5,9 @@ "react": "https://esm.sh/stable/preact@10.13.2/compat", "preact": "https://esm.sh/stable/preact@10.13.2/", "@deep/": "./deep/", - "@eno/app": "./app.tsx", "@eno/iso": "http://localhost:4507/lib/iso.tsx" }, "tasks": { - "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 app.tsx" } } \ No newline at end of file diff --git a/fetch.test.tsx b/fetch.test.tsx deleted file mode 100644 index 1a16c21..0000000 --- a/fetch.test.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import {Fetch} from "./lib/iso.tsx"; - - -const delay =async(inHandler:()=>void, inDelay:number):Promise=> -{ - return new Promise((accept)=>{ - setTimeout(()=>{ - accept(inHandler()); - }, inDelay); - }); -}; - -const queue = [1, 2, 3]; -while(queue.length) -{ - await(delay(()=>{console.log(queue.pop())}, 1000)) -} - -console.log("all done!"); - -/* -let r1, r2, r3; - -delay(()=>{r1 = Fetch.Request(`https://catfact.ninja/fact`, undefined, 0.2); console.log(r1); }, 10); -delay(()=>{r2 = Fetch.Request(`https://catfact.ninja/fact`, undefined, 0.2); console.log(r2); }, 20); -delay(()=>{r3 = Fetch.Request(`https://catfact.ninja/fact`, undefined, 0.2); console.log(r3); }, 2000); - - -await delay(()=>{}, 3000); - -console.log(r1); -console.log(r2); -console.log(r3); - -*/ \ No newline at end of file diff --git a/lib/boot-client.tsx b/lib/boot-client.tsx new file mode 100644 index 0000000..6eb1f15 --- /dev/null +++ b/lib/boot-client.tsx @@ -0,0 +1,30 @@ +import React, {hydrate} from "react"; +import * as Twind from "https://esm.sh/v115/@twind/core@1.1.3/es2022/core.mjs"; +import {Router, CSS, Meta} from "@eno/iso"; + +export function Boot(inApp:()=>React.JSX.Element, inCSS?:object) +{ + console.log(inApp, inCSS); + Twind.install(inCSS ? {...CSS, ...inCSS} : CSS); + + const HMRWrap =()=> React.createElement(inApp, null, null); + + const root = document.querySelector("#app"); + + if(root) + { + hydrate( + + + + + , + root + ); + } + else + { + console.log(`no "#app" element is present!`) + } + +}; \ No newline at end of file diff --git a/lib/iso.tsx b/lib/iso.tsx index 42b621b..4747787 100644 --- a/lib/iso.tsx +++ b/lib/iso.tsx @@ -1,17 +1,20 @@ 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 { Boot as _Boot } from "../server.tsx"; export const CSS = { presets: [TWPreTail(), TWPreAuto()], hash:false }; -if(!window.innerWidth) + +export function Boot(inApp:React.FunctionComponent, inCSS?:object) { - import(import.meta.resolve("../../server.tsx")).then(()=>{console.log("...imported!");}); + _Boot(inApp, inCSS); } + type MetasInputs = { [Property in MetaKeys]?: string }; type MetasModeArgs = {concatListed?:boolean; dropUnlisted?:boolean}; type MetasStackItem = MetasModeArgs&MetasInputs&{id:string, depth:number} diff --git a/lib/mid.tsx b/lib/mid.tsx new file mode 100644 index 0000000..e69de29 diff --git a/server.tsx b/server.tsx index f912b27..0928e4d 100644 --- a/server.tsx +++ b/server.tsx @@ -2,14 +2,13 @@ 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 { 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 { toFileUrl } from "https://deno.land/std@0.185.0/path/mod.ts"; 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 * as Twind from "https://esm.sh/@twind/core@1.1.3"; import React from "react"; import * as Iso from "@eno/iso"; -Deno.env.set("initialized", "true"); - /** * Setup a transpiler. * @param inDevMode When true, starts a file-watcher @@ -134,6 +133,7 @@ function Transpiler(inDevMode:boolean) } +type ImportMap = {imports?:Record, importMap?:string}; /** * Extract all configuration info form a project's deno.jsonc file * @param inDevMode When true, proxies react to an HMR-enabled version @@ -142,8 +142,7 @@ function Transpiler(inDevMode:boolean) */ async function Configure(inDevMode:boolean, inLibPath:string) { - type ImportMap = {imports?:Record, importMap?:string}; - const output:{Imports?:ImportMap, App?:React.FunctionComponent, TwindInst?:Twind.Twind, Error?:string} = {}; + const output:{Imports?:ImportMap, Error?:string} = {}; let ImportObject:ImportMap = {}; try { @@ -204,52 +203,6 @@ async function Configure(inDevMode:boolean, inLibPath:string) 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) @@ -272,158 +225,180 @@ async function Configure(inDevMode:boolean, inLibPath:string) return output; } -const Flags:Record = {}; Deno.args.forEach(arg=> { if(arg.startsWith("--")) { const kvp = arg.substring(2).split("="); - Flags[kvp[0]] = kvp[1] ? kvp[1] : true; + Deno.env.set(kvp[0], kvp[1] ? kvp[1] : "true"); } }); -let DevMode = Flags.dev ? true : false; +let DevMode = Deno.env.get("dev") ? true : false; let hosted = import.meta.resolve("./"); const Path = { Hosted: hosted.substring(0, hosted.length-1), Active: `file://${Deno.cwd().replaceAll("\\", "/")}`, - LibDir: "lib" + LibDir: "lib", + AppDir: "" }; console.log(Path); -console.log(`Dev Mode: ${DevMode}`); -console.log(`Args seen: `, Flags); +console.log(`Dev Mode:`, DevMode); +console.log(`import.meta.url:`, import.meta.url); +console.log(`Deno.cwd():`, Deno.cwd()); +console.log(`Deno.mainModule:`, Deno.mainModule); -const {Transpileable, TranspileURL, SocketsHandler} = Transpiler(DevMode); -const {Imports, App, TwindInst, Error} = await Configure(DevMode, Path.LibDir); -if(Error) + +let Booted = false; +let TwindInst:Twind.Twind; +export function Boot(inApp:React.FunctionComponent, inCSS?:object) { - console.log(Error); + if(Booted){return;} + Booted = true; + + const pathInit = Deno.mainModule; + const pathProj = toFileUrl(Deno.cwd()); + + //@ts-ignore + TwindInst = Twind.install({...Iso.CSS, ...inCSS||{}}); + + const App = inApp; + Path.AppDir = pathInit.split(pathProj.toString())[1]; + + Server(App, Path.AppDir, TwindInst); } -else if(App && TwindInst) + +async function Server(App:React.FunctionComponent, AppPath:string, TwindInst:Twind.Twind) { - Deno.serve({ port: Flags?.port||3000 }, async(_req:Request) => + const {Transpileable, TranspileURL, SocketsHandler} = Transpiler(DevMode); + const {Imports, Error} = await Configure(DevMode, Path.LibDir); + if(Error) { - 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 + console.log(Error); + } + else if(App && TwindInst) + { + Deno.serve({ port: Deno.env.get("port")||3000 }, async(_req:Request) => { - // serve index by default - let type = `text/html`; - let body:BodyInit = ``; - - const isLib = url.pathname.startsWith(`/${Path.LibDir}/`); - - if(Transpileable(url.pathname)) + 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; } + + console.log(url.pathname); + + try { - type = `application/javascript`; - if(isLib) + // serve index by default + let type = `text/html`; + let body:BodyInit = ``; + + const isLib = url.pathname.startsWith(`/${Path.LibDir}/`); + + if(Transpileable(url.pathname)) { - body = await TranspileURL(Path.Hosted+url.pathname, url.pathname, true); + type = `application/javascript`; + if(isLib) + { + body = await TranspileURL(Path.Hosted+url.pathname, url.pathname, true); + } + else if(url.pathname == "/server.tsx") + { + body = await TranspileURL(`${Path.Hosted}/${Path.LibDir}/boot-client.tsx`, url.pathname, true); + } + else if(DevMode && !url.searchParams.get("reload")) + { + const imp = await import(Path.Active+url.pathname); + const members = []; + for( const key in imp ) { members.push(key); } + body = + ` + 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} }; + `).join(" ") } + const reloadHandler = (updatedModule)=> + { + ${ members.map(m=>`proxy_${m} = updatedModule.${m};`).join("\n") } + }; + FileListen("${url.pathname}", reloadHandler);`; + + } + else + { + body = await TranspileURL(Path.Active+url.pathname, url.pathname, true); + } } - else if(DevMode && !url.searchParams.get("reload")) + // serve static media + else if( pathExt ) { - const imp = await import(Path.Active+url.pathname); - const members = []; - for( const key in imp ) { members.push(key); } - body = - ` - 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} }; - `).join(" ") } - const reloadHandler = (updatedModule)=> - { - ${ members.map(m=>`proxy_${m} = updatedModule.${m};`).join("\n") } - }; - FileListen("${url.pathname}", reloadHandler);`; - + type = MIME.typeByExtension(pathExt) || "text/html"; + const _fetch = await fetch((Path.Active)+url.pathname); + body = await _fetch.text(); } else { - body = await TranspileURL(Path.Active+url.pathname, url.pathname, true); - } - } - // serve static media - else if( pathExt ) - { - type = MIME.typeByExtension(pathExt) || "text/html"; - const _fetch = await fetch((Path.Active)+url.pathname); - body = await _fetch.text(); - } - else - { - Iso.Fetch.ServerBlocking = []; - Iso.Fetch.ServerTouched = new Set(); - Iso.Fetch.ServerRemove = new Set(); - let app = ; - await Prepass(app) - let bake = SSR(app); - while(Iso.Fetch.ServerBlocking.length) - { - await Promise.all(Iso.Fetch.ServerBlocking); Iso.Fetch.ServerBlocking = []; - // 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. - app = ; + Iso.Fetch.ServerTouched = new Set(); + Iso.Fetch.ServerRemove = new Set(); + let app = ; await Prepass(app) - bake = SSR(app); + let bake = SSR(app); + while(Iso.Fetch.ServerBlocking.length) + { + await Promise.all(Iso.Fetch.ServerBlocking); + Iso.Fetch.ServerBlocking = []; + // 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. + app = ; + await Prepass(app) + bake = SSR(app); + } + + const seed:Iso.FetchRecord[] = []; + Iso.Fetch.ServerTouched.forEach((record)=>{ + const r:Iso.FetchRecord = {...record}; + delete r.Promise; + seed.push(r); + }); + Iso.Fetch.ServerTouched = false; + + const results = Twind.extract(bake, TwindInst); + + type = `text/html`; + body = + ` + + + ${Iso.Meta.Meta.title} + + + + + + +
${results.html}
+ + + `; } - - const seed:Iso.FetchRecord[] = []; - Iso.Fetch.ServerTouched.forEach((record)=>{ - const r:Iso.FetchRecord = {...record}; - delete r.Promise; - seed.push(r); - }); - Iso.Fetch.ServerTouched = false; - - const results = Twind.extract(bake, TwindInst); - - type = `text/html`; - body = - ` - - - ${Iso.Meta.Meta.title} - - - - - - -
${results.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"}}); - } - catch(error) - { - console.log(error); - return new Response(error, {status:404}); - } - }); -} \ No newline at end of file + catch(error) + { + console.log(error); + return new Response(error, {status:404}); + } + }); + } +}