diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..a0c25b9
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,24 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "request": "launch",
+ "name": "Launch Program",
+ "type": "node",
+ "program": "${workspaceFolder}/server.tsx",
+ "cwd": "${workspaceFolder}",
+ "runtimeExecutable": "C:\\Users\\Seth\\.deno\\bin\\deno.EXE",
+ "runtimeArgs": [
+ "run",
+ "--no-lock",
+ "--unstable",
+ "--inspect-wait",
+ "--allow-all"
+ ],
+ "attachSimplePort": 9229
+ }
+ ]
+}
\ No newline at end of file
diff --git a/deno.lock b/deno.lock
new file mode 100644
index 0000000..b8e174d
--- /dev/null
+++ b/deno.lock
@@ -0,0 +1,16 @@
+{
+ "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 ea4529d..2f4abe9 100644
--- a/example/app.tsx
+++ b/example/app.tsx
@@ -1,16 +1,24 @@
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 Component from "./deep/component.tsx";
import * as Iso from "@eno/iso";
+const Comp = React.lazy(()=>import("./deep/component.tsx"));
+
export default ()=>
{
return
-
Title!!
-
subtitle
-
+
+
+
Title!!
+
suspended:
+
Loading! }>
+
+
diff --git a/example/deep/component.tsx b/example/deep/component.tsx
index 124ecfc..7fa1bf2 100644
--- a/example/deep/component.tsx
+++ b/example/deep/component.tsx
@@ -4,14 +4,20 @@ import * as Iso from "@eno/iso";
export default ()=>
{
const [countGet, countSet] = React.useState(1);
-
const [routeGet, routeSet] = Iso.Router.Consumer();
+ type CatFact = {fact:string, length:number}|undefined;
+ const [Data, Updating] = Iso.Fetch.Use(`https://catfact.ninja/fact`);
+
+
+ console.log("render!!")
+
return
- Route is: {routeGet.Path.toString()}
-
- Component!!!
+ Component Route is: {routeGet.Path.toString()}
+
a link
+
Data:{Data && (Data as CatFact)?.fact}
+
Status:{Updating?'loading':'done'}
;
};
\ No newline at end of file
diff --git a/fetch.test.tsx b/fetch.test.tsx
new file mode 100644
index 0000000..1a16c21
--- /dev/null
+++ b/fetch.test.tsx
@@ -0,0 +1,35 @@
+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/iso.tsx b/lib/iso.tsx
index 2b1789a..1bb54ea 100644
--- a/lib/iso.tsx
+++ b/lib/iso.tsx
@@ -158,4 +158,113 @@ export const Switch =({children}:{children:Children})=>
return fallback;
};
export const Case =({children, value}:{children:Children, value?:string, default?:true})=>null;
-export const useRouteVars =()=> React.useContext(SwitchContext).keys;
\ No newline at end of file
+export const useRouteVars =()=> React.useContext(SwitchContext).keys;
+
+export type FetchCachOptions = {CacheFor:number, CacheOnServer:boolean, DelaySSR:boolean, Seed:boolean};
+export type FetchRecord = {URL:string, Promise?:Promise, CachedAt:number, Error?:string, JSON?:object} & FetchCachOptions;
+type FetchGuide = [Record:FetchRecord, Init:boolean, Listen:boolean];
+export type FetchHookState = [Data:undefined|object, Updating:boolean];
+export const Fetch = {
+ Cache:new Map() as Map,
+ ServerBlocking:false as false|Promise[],
+ ServerTouched:false as false|Set,
+ ServerRemove:false as false|Set,
+ Seed(seed:FetchRecord[])
+ {
+ seed.forEach(r=>{
+ //r.Promise = Promise.resolve(r);
+ Fetch.Cache.set(r.URL, r)
+ });
+ },
+ DefaultOptions:{CacheFor:60, CacheOnServer:true, DelaySSR:true, Seed:true} as FetchCachOptions,
+ Request(URL:string, Init?:RequestInit|null, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true, Seed:boolean = true):FetchGuide
+ {
+ let check = Fetch.Cache.get(URL);
+
+ const load =(inCheck:FetchRecord)=>
+ {
+ Fetch.Cache.set(URL, inCheck);
+ inCheck.CachedAt = 0;
+ inCheck.Promise = fetch(URL, Init?Init:undefined).then(resp=>resp.json()).then((json)=>{
+ inCheck.JSON = json;
+ inCheck.CachedAt = new Date().getTime();
+ console.log(`...cached!`);
+ return inCheck;
+ });
+ return inCheck;
+ };
+
+ if(!check)
+ {
+ // not in the cache
+ // - listen
+
+ console.log(`making new cache record...`);
+ return [load({URL, CacheFor, CachedAt:0, CacheOnServer, DelaySSR, Seed}), false, true];
+ }
+ else if(check.CachedAt == 0)
+ {
+ // loading started but not finished
+ // - listen
+ // - possibly init if there is something in JSON
+
+ console.log(`currently being cached...`);
+ return [check, check.JSON ? true : false, true];
+ }
+ else
+ {
+ console.log(`found in cache...`);
+ let secondsAge = (new Date().getTime() - check.CachedAt)/1000;
+ if(secondsAge > check.CacheFor)
+ {
+ // cached but expired
+ // - listen
+ // - init
+ console.log(`...outdated...`);
+ return [load(check), true, true];
+ }
+ else
+ {
+ // cached and ready
+ // - init
+ console.log(`...retrieved!`);
+ return [check, true, false];
+ }
+
+ }
+ },
+
+ Use(URL:string, Init?:RequestInit|null, Options?:FetchCachOptions)
+ {
+ const config = {...Fetch.DefaultOptions, ...Options};
+ const [receipt, init, listen] = Fetch.Request(URL, Init, config.CacheFor, config.CacheOnServer, config.DelaySSR, config.Seed);
+ const initialState:FetchHookState = init ? [receipt.JSON, listen] : [undefined, true];
+ const [cacheGet, cacheSet] = React.useState(initialState);
+
+ if(Fetch.ServerBlocking && Fetch.ServerTouched && config.DelaySSR) // if server-side rendering
+ {
+ if(listen) // if the request is pending
+ {
+ receipt.Promise && Fetch.ServerBlocking.push(receipt.Promise); // add promise to blocking list
+ return [undefined, listen] as FetchHookState; // no need to return any actual data while waiting server-side
+ }
+ else // if request is ready
+ {
+ receipt.Seed && Fetch.ServerTouched.add(receipt); // add record to client seed list (if specified in receipt.seed)
+ return [receipt.JSON, false] as FetchHookState;
+ }
+ }
+
+ React.useEffect(()=>
+ {
+ if(listen)
+ {
+ //const receipt = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR);
+ receipt.Promise?.then(()=>cacheSet([receipt.JSON, receipt.CachedAt === 0]));
+ }
+ }
+ , []);
+
+ return cacheGet;
+ }
+};
\ No newline at end of file
diff --git a/server.tsx b/server.tsx
index 300d9c6..385f1ff 100644
--- a/server.tsx
+++ b/server.tsx
@@ -2,6 +2,7 @@ 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";
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";
@@ -35,7 +36,8 @@ const Transpileable =(inFilePath:string):boolean=>
};
const Transpile =async(inCode:string, inKey:string):Promise=>
{
- const transpile = await ESBuild.transform(inCode, { loader: "tsx", minify:true});
+ const transpile = await ESBuild.transform(inCode, { loader: "tsx", sourcemap:"inline", minify:true, sourcefile:inKey});
+
Transpiled.set(inKey, transpile.code);
return transpile.code;
};
@@ -221,10 +223,36 @@ FileListen("${url.pathname}", reloadHandler);`;
}
else
{
- const results = Twind.extract(SSR(), TwindInst);
+ 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 = ;
+ 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 = `
+ body =
+`
${Iso.Meta.title}
@@ -236,12 +264,14 @@ FileListen("${url.pathname}", reloadHandler);`;
${results.html}
`;