From 4730cdec3e07a1d76c8d6f321c954644e7a41969 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Tue, 18 Apr 2023 22:22:59 -0400 Subject: [PATCH 1/9] fetch cache started --- deno.lock | 16 +++++++++++++++ fetch.test.tsx | 24 +++++++++++++++++++++++ lib/iso.tsx | 53 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 deno.lock create mode 100644 fetch.test.tsx 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/fetch.test.tsx b/fetch.test.tsx new file mode 100644 index 0000000..1053023 --- /dev/null +++ b/fetch.test.tsx @@ -0,0 +1,24 @@ +import {Fetch} from "./lib/iso.tsx"; + + +const delay =async(inHandler:()=>void, inDelay:number):Promise=> +{ + return new Promise((accept)=>{ + setTimeout(()=>{ + accept(inHandler()); + }, inDelay); + }); +}; + +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..bbc5be1 100644 --- a/lib/iso.tsx +++ b/lib/iso.tsx @@ -158,4 +158,55 @@ 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; + + +type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, DelaySSR:boolean, Error:boolean, Text:false|string}; +export const Fetch = { + Cache:new Map() as Map, + async Request(URL:string, Init?:RequestInit, CacheFor:number = 60, DelaySSR:boolean = true) + { + let check = Fetch.Cache.get(URL); + + const load =async(inCheck:FetchRecord)=> + { + Fetch.Cache.set(URL, inCheck); + const resp = await fetch(URL, Init); + inCheck.Text = await resp.text(); + inCheck.CachedAt = new Date().getTime(); + console.log(`...cached!`); + return inCheck; + } + + if(!check) + { + console.log(`making new cache record...`); + check = {URL, CacheFor, CachedAt:0, DelaySSR, Error:false, Text:false}; + load(check); + return check; + } + if(check.CachedAt == 0) + { + console.log(`currently being cached...`); + return check; + } + else if(check?.Text) + { + console.log(`found in cache...`); + let secondsAge = Math.floor((new Date().getTime() - check.CachedAt)/1000); + if(secondsAge > check.CacheFor) + { + console.log(`...outdated...`); + check.CachedAt = 0; + load(check); + return check; + } + else + { + console.log(`...retrieved!`); + return check; + } + + } + } +}; \ No newline at end of file From 7847410f3e6c624bc3461312681b9afb382c7010 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Wed, 19 Apr 2023 20:56:30 -0400 Subject: [PATCH 2/9] fetch hook started --- example/deep/component.tsx | 6 ++++- fetch.test.tsx | 13 +++++++++- lib/iso.tsx | 53 +++++++++++++++++++++++++------------- 3 files changed, 52 insertions(+), 20 deletions(-) diff --git a/example/deep/component.tsx b/example/deep/component.tsx index 124ecfc..f588cf3 100644 --- a/example/deep/component.tsx +++ b/example/deep/component.tsx @@ -4,14 +4,18 @@ 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`); + return
Route is: {routeGet.Path.toString()} Component!!! 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 index 1053023..8487a95 100644 --- a/fetch.test.tsx +++ b/fetch.test.tsx @@ -10,6 +10,15 @@ const delay =async(inHandler:()=>void, inDelay:number):Promise=> }); }; +const cacheRecord = {Promise:delay(()=>{}, 1000)}; + +cacheRecord.Promise.then(()=>{console.log(`handler one`)}); +cacheRecord.Promise.then(()=>{console.log(`handler two`)}); + +cacheRecord.Promise = delay(()=>{}, 3000); + + +/* let r1, r2, r3; delay(()=>{r1 = Fetch.Request(`https://catfact.ninja/fact`, undefined, 0.2); console.log(r1); }, 10); @@ -21,4 +30,6 @@ await delay(()=>{}, 3000); console.log(r1); console.log(r2); -console.log(r3); \ No newline at end of file +console.log(r3); + +*/ \ No newline at end of file diff --git a/lib/iso.tsx b/lib/iso.tsx index bbc5be1..bdd2f90 100644 --- a/lib/iso.tsx +++ b/lib/iso.tsx @@ -160,46 +160,44 @@ export const Switch =({children}:{children:Children})=> export const Case =({children, value}:{children:Children, value?:string, default?:true})=>null; export const useRouteVars =()=> React.useContext(SwitchContext).keys; - -type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, DelaySSR:boolean, Error:boolean, Text:false|string}; +type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, Promise?:Promise, DelaySSR:boolean, Error?:string, JSON?:object}; export const Fetch = { Cache:new Map() as Map, - async Request(URL:string, Init?:RequestInit, CacheFor:number = 60, DelaySSR:boolean = true) + Request(URL:string, Init?:RequestInit, CacheFor:number = 60, DelaySSR:boolean = true) { let check = Fetch.Cache.get(URL); - const load =async(inCheck:FetchRecord)=> + const load =(inCheck:FetchRecord)=> { Fetch.Cache.set(URL, inCheck); - const resp = await fetch(URL, Init); - inCheck.Text = await resp.text(); - inCheck.CachedAt = new Date().getTime(); - console.log(`...cached!`); + inCheck.CachedAt = 0; + inCheck.Promise = fetch(URL, Init).then(resp=>resp.json()).then((json)=>{ + inCheck.JSON = json; + inCheck.CachedAt = new Date().getTime(); + console.log(`...cached!`); + return inCheck; + }); return inCheck; } if(!check) { console.log(`making new cache record...`); - check = {URL, CacheFor, CachedAt:0, DelaySSR, Error:false, Text:false}; - load(check); - return check; + return load({URL, CacheFor, CachedAt:0, DelaySSR}); } - if(check.CachedAt == 0) + else if(check.CachedAt == 0) { console.log(`currently being cached...`); return check; } - else if(check?.Text) + else { console.log(`found in cache...`); - let secondsAge = Math.floor((new Date().getTime() - check.CachedAt)/1000); + let secondsAge = (new Date().getTime() - check.CachedAt)/1000; if(secondsAge > check.CacheFor) { console.log(`...outdated...`); - check.CachedAt = 0; - load(check); - return check; + return load(check); } else { @@ -208,5 +206,24 @@ export const Fetch = { } } + }, + Use(URL:string, Init?:RequestInit, CacheFor:number = 60, DelaySSR:boolean = true) + { + type FetchHookState = [Data:undefined|object, Updating:boolean]; + const [cacheGet, cacheSet] = React.useState([undefined, true] as FetchHookState); + React.useEffect(()=> + { + const receipt = Fetch.Request(URL, Init, CacheFor, DelaySSR); + receipt.Promise?.then(()=>{ + ++count; + if(count > 10){return;} + console.log(`updating state`, receipt); + cacheSet([receipt.JSON, receipt.CachedAt === 0]); + }); + } + , []); + + return cacheGet; } -}; \ No newline at end of file +}; +let count = 0; \ No newline at end of file From 866eb9b26dc51f647285bb8d8fa87de35eeb5d62 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Thu, 20 Apr 2023 21:07:26 -0400 Subject: [PATCH 3/9] ssr data batches --- .vscode/launch.json | 23 +++++++++++++++++++++++ fetch.test.tsx | 12 ++++++------ lib/iso.tsx | 31 ++++++++++++++++++------------- server.tsx | 13 ++++++++++++- 4 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..179eb6d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + // 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", + "--unstable", + "--inspect-wait", + "--allow-all" + ], + "attachSimplePort": 9229 + } + ] +} \ No newline at end of file diff --git a/fetch.test.tsx b/fetch.test.tsx index 8487a95..1a16c21 100644 --- a/fetch.test.tsx +++ b/fetch.test.tsx @@ -10,13 +10,13 @@ const delay =async(inHandler:()=>void, inDelay:number):Promise=> }); }; -const cacheRecord = {Promise:delay(()=>{}, 1000)}; - -cacheRecord.Promise.then(()=>{console.log(`handler one`)}); -cacheRecord.Promise.then(()=>{console.log(`handler two`)}); - -cacheRecord.Promise = delay(()=>{}, 3000); +const queue = [1, 2, 3]; +while(queue.length) +{ + await(delay(()=>{console.log(queue.pop())}, 1000)) +} +console.log("all done!"); /* let r1, r2, r3; diff --git a/lib/iso.tsx b/lib/iso.tsx index bdd2f90..a9030bc 100644 --- a/lib/iso.tsx +++ b/lib/iso.tsx @@ -160,10 +160,12 @@ export const Switch =({children}:{children:Children})=> export const Case =({children, value}:{children:Children, value?:string, default?:true})=>null; export const useRouteVars =()=> React.useContext(SwitchContext).keys; -type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, Promise?:Promise, DelaySSR:boolean, Error?:string, JSON?:object}; +type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, CacheOnServer:boolean, Promise?:Promise, DelaySSR:boolean, Error?:string, JSON?:object}; + export const Fetch = { Cache:new Map() as Map, - Request(URL:string, Init?:RequestInit, CacheFor:number = 60, DelaySSR:boolean = true) + SSR:false as false|Promise[], + Request(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true) { let check = Fetch.Cache.get(URL); @@ -183,7 +185,7 @@ export const Fetch = { if(!check) { console.log(`making new cache record...`); - return load({URL, CacheFor, CachedAt:0, DelaySSR}); + return load({URL, CacheFor, CachedAt:0, CacheOnServer, DelaySSR}); } else if(check.CachedAt == 0) { @@ -207,23 +209,26 @@ export const Fetch = { } }, - Use(URL:string, Init?:RequestInit, CacheFor:number = 60, DelaySSR:boolean = true) + Use(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true) { type FetchHookState = [Data:undefined|object, Updating:boolean]; const [cacheGet, cacheSet] = React.useState([undefined, true] as FetchHookState); + if(Fetch.SSR && DelaySSR) + { + const receipt = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR); + if(receipt.CachedAt === 0 && receipt.Promise) + { + Fetch.SSR.push(receipt.Promise); + } + return [receipt.JSON, receipt.CachedAt === 0]; + } React.useEffect(()=> { - const receipt = Fetch.Request(URL, Init, CacheFor, DelaySSR); - receipt.Promise?.then(()=>{ - ++count; - if(count > 10){return;} - console.log(`updating state`, receipt); - cacheSet([receipt.JSON, receipt.CachedAt === 0]); - }); + const receipt = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR); + receipt.Promise?.then(()=>cacheSet([receipt.JSON, receipt.CachedAt === 0])); } , []); return cacheGet; } -}; -let count = 0; \ No newline at end of file +}; \ No newline at end of file diff --git a/server.tsx b/server.tsx index 300d9c6..986c043 100644 --- a/server.tsx +++ b/server.tsx @@ -221,7 +221,18 @@ FileListen("${url.pathname}", reloadHandler);`; } else { - const results = Twind.extract(SSR(), TwindInst); + let bake = ""; + Iso.Fetch.SSR = []; + bake = SSR(); + + while(Iso.Fetch.SSR.length) + { + await Promise.all(Iso.Fetch.SSR); + Iso.Fetch.SSR = []; + bake = SSR(); + } + + const results = Twind.extract(bake, TwindInst); type = `text/html`; body = ` From 26cb46923fead330d205950a7b3ac06707c159e7 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 21 Apr 2023 20:27:37 -0400 Subject: [PATCH 4/9] seed the client cache --- .vscode/launch.json | 1 + lib/iso.tsx | 21 ++++++++++++++++----- server.tsx | 27 ++++++++++++++++++--------- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 179eb6d..a0c25b9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -13,6 +13,7 @@ "runtimeExecutable": "C:\\Users\\Seth\\.deno\\bin\\deno.EXE", "runtimeArgs": [ "run", + "--no-lock", "--unstable", "--inspect-wait", "--allow-all" diff --git a/lib/iso.tsx b/lib/iso.tsx index a9030bc..f6f07c8 100644 --- a/lib/iso.tsx +++ b/lib/iso.tsx @@ -160,11 +160,18 @@ export const Switch =({children}:{children:Children})=> export const Case =({children, value}:{children:Children, value?:string, default?:true})=>null; export const useRouteVars =()=> React.useContext(SwitchContext).keys; -type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, CacheOnServer:boolean, Promise?:Promise, DelaySSR:boolean, Error?:string, JSON?:object}; - +export type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, CacheOnServer:boolean, Promise?:Promise, DelaySSR:boolean, Error?:string, JSON?:object}; export const Fetch = { Cache:new Map() as Map, - SSR:false as false|Promise[], + ServerBlocking:false as false|Promise[], + ServerTouched:new Set() as Set, + Seed(seed:FetchRecord[]) + { + seed.forEach(r=>{ + r.Promise = Promise.resolve(r); + Fetch.Cache.set(r.URL, r) + }); + }, Request(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true) { let check = Fetch.Cache.get(URL); @@ -213,12 +220,16 @@ export const Fetch = { { type FetchHookState = [Data:undefined|object, Updating:boolean]; const [cacheGet, cacheSet] = React.useState([undefined, true] as FetchHookState); - if(Fetch.SSR && DelaySSR) + if(Fetch.ServerBlocking && DelaySSR) { const receipt = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR); if(receipt.CachedAt === 0 && receipt.Promise) { - Fetch.SSR.push(receipt.Promise); + Fetch.ServerBlocking.push(receipt.Promise); + } + else + { + Fetch.ServerTouched.add(receipt); } return [receipt.JSON, receipt.CachedAt === 0]; } diff --git a/server.tsx b/server.tsx index 986c043..68465b7 100644 --- a/server.tsx +++ b/server.tsx @@ -221,21 +221,29 @@ FileListen("${url.pathname}", reloadHandler);`; } else { - let bake = ""; - Iso.Fetch.SSR = []; - bake = SSR(); - - while(Iso.Fetch.SSR.length) + Iso.Fetch.ServerBlocking = []; + let bake = SSR(); + while(Iso.Fetch.ServerBlocking.length) { - await Promise.all(Iso.Fetch.SSR); - Iso.Fetch.SSR = []; + await Promise.all(Iso.Fetch.ServerBlocking); + Iso.Fetch.ServerBlocking = []; bake = SSR(); } + const seed:Iso.FetchRecord[] = []; + Iso.Fetch.ServerTouched.forEach((record)=>{ + + const r:Iso.FetchRecord = {...record}; + delete r.Promise; + seed.push(r); + }); + Iso.Fetch.ServerTouched = new Set(); + const results = Twind.extract(bake, TwindInst); type = `text/html`; - body = ` + body = +` ${Iso.Meta.title} @@ -250,8 +258,9 @@ FileListen("${url.pathname}", reloadHandler);`; import {render, createElement as H} from "react"; 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 {Router} from "@eno/iso"; + import {Router, Fetch} from "@eno/iso"; Twind.install(CSS); + Fetch.Seed(${JSON.stringify(seed)}); render( H(Router.Provider, null, H(App)), document.querySelector("#app")); From 1ed3e0abaa272d834d084eb43b70f35b77265766 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Fri, 21 Apr 2023 22:21:04 -0400 Subject: [PATCH 5/9] server record clearing --- lib/iso.tsx | 2 +- server.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/iso.tsx b/lib/iso.tsx index f6f07c8..ab1fb2e 100644 --- a/lib/iso.tsx +++ b/lib/iso.tsx @@ -164,7 +164,7 @@ export type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, CacheOn export const Fetch = { Cache:new Map() as Map, ServerBlocking:false as false|Promise[], - ServerTouched:new Set() as Set, + ServerTouched:false as false|Set, Seed(seed:FetchRecord[]) { seed.forEach(r=>{ diff --git a/server.tsx b/server.tsx index 68465b7..a6efd9a 100644 --- a/server.tsx +++ b/server.tsx @@ -222,6 +222,7 @@ FileListen("${url.pathname}", reloadHandler);`; else { Iso.Fetch.ServerBlocking = []; + Iso.Fetch.ServerTouched = new Set(); let bake = SSR(); while(Iso.Fetch.ServerBlocking.length) { @@ -237,7 +238,7 @@ FileListen("${url.pathname}", reloadHandler);`; delete r.Promise; seed.push(r); }); - Iso.Fetch.ServerTouched = new Set(); + Iso.Fetch.ServerTouched = false; const results = Twind.extract(bake, TwindInst); From bdeb51a940cf428ec5f29be500cb20ec0e9d7e7b Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Sat, 22 Apr 2023 06:22:53 -0400 Subject: [PATCH 6/9] control client seeding --- lib/iso.tsx | 17 +++++++++-------- server.tsx | 4 +++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/iso.tsx b/lib/iso.tsx index ab1fb2e..071cbd7 100644 --- a/lib/iso.tsx +++ b/lib/iso.tsx @@ -160,11 +160,12 @@ export const Switch =({children}:{children:Children})=> export const Case =({children, value}:{children:Children, value?:string, default?:true})=>null; export const useRouteVars =()=> React.useContext(SwitchContext).keys; -export type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, CacheOnServer:boolean, Promise?:Promise, DelaySSR:boolean, Error?:string, JSON?:object}; +export type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, CacheOnServer:boolean, Promise?:Promise, DelaySSR:boolean, Seed:boolean, Error?:string, JSON?:object}; 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=>{ @@ -172,7 +173,7 @@ export const Fetch = { Fetch.Cache.set(r.URL, r) }); }, - Request(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true) + Request(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true, Seed:boolean = true) { let check = Fetch.Cache.get(URL); @@ -192,7 +193,7 @@ export const Fetch = { if(!check) { console.log(`making new cache record...`); - return load({URL, CacheFor, CachedAt:0, CacheOnServer, DelaySSR}); + return load({URL, CacheFor, CachedAt:0, CacheOnServer, DelaySSR, Seed}); } else if(check.CachedAt == 0) { @@ -216,18 +217,18 @@ export const Fetch = { } }, - Use(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true) + Use(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true, Seed:boolean = true) { type FetchHookState = [Data:undefined|object, Updating:boolean]; const [cacheGet, cacheSet] = React.useState([undefined, true] as FetchHookState); - if(Fetch.ServerBlocking && DelaySSR) + if(Fetch.ServerBlocking && Fetch.ServerTouched && DelaySSR) { - const receipt = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR); - if(receipt.CachedAt === 0 && receipt.Promise) + const receipt = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR, Seed); + if(receipt.CachedAt === 0 && receipt.Promise)// request for something that hasnt been cached yet { Fetch.ServerBlocking.push(receipt.Promise); } - else + else if(receipt.Seed) { Fetch.ServerTouched.add(receipt); } diff --git a/server.tsx b/server.tsx index a6efd9a..2aae3d6 100644 --- a/server.tsx +++ b/server.tsx @@ -223,17 +223,19 @@ FileListen("${url.pathname}", reloadHandler);`; { Iso.Fetch.ServerBlocking = []; Iso.Fetch.ServerTouched = new Set(); + Iso.Fetch.ServerRemove = new Set(); let bake = SSR(); 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. bake = SSR(); } const seed:Iso.FetchRecord[] = []; Iso.Fetch.ServerTouched.forEach((record)=>{ - const r:Iso.FetchRecord = {...record}; delete r.Promise; seed.push(r); From 63137a7726c4b00a6be5666dff64e6340f00e963 Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Sat, 22 Apr 2023 12:08:58 -0400 Subject: [PATCH 7/9] fix flickering --- example/app.tsx | 9 ++++-- example/deep/component.tsx | 8 ++++-- lib/iso.tsx | 56 ++++++++++++++++++++++++++------------ server.tsx | 16 +++++++---- 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/example/app.tsx b/example/app.tsx index ea4529d..c785b1f 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -1,16 +1,19 @@ 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

- +

suspended:

+ Loading!
}> + + diff --git a/example/deep/component.tsx b/example/deep/component.tsx index f588cf3..a93ae9a 100644 --- a/example/deep/component.tsx +++ b/example/deep/component.tsx @@ -7,13 +7,15 @@ export default ()=> const [routeGet, routeSet] = Iso.Router.Consumer(); type CatFact = {fact:string, length:number}|undefined; - const [Data, Updating] = Iso.Fetch.Use(`https://catfact.ninja/fact`); + const [Data, Updating] = Iso.Fetch.Use(`https://catfact.ninja/fact`, undefined, 60, true, true, true); + + + console.log("render!!") return
- Route is: {routeGet.Path.toString()} + Component Route is: {routeGet.Path.toString()} - Component!!! a link

Data:{Data && (Data as CatFact)?.fact}

Status:{Updating?'loading':'done'}

diff --git a/lib/iso.tsx b/lib/iso.tsx index 071cbd7..2989821 100644 --- a/lib/iso.tsx +++ b/lib/iso.tsx @@ -161,6 +161,8 @@ export const Case =({children, value}:{children:Children, value?:string, default export const useRouteVars =()=> React.useContext(SwitchContext).keys; export type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, CacheOnServer:boolean, Promise?:Promise, DelaySSR:boolean, Seed:boolean, Error?:string, JSON?:object}; +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[], @@ -169,11 +171,11 @@ export const Fetch = { Seed(seed:FetchRecord[]) { seed.forEach(r=>{ - r.Promise = Promise.resolve(r); + //r.Promise = Promise.resolve(r); Fetch.Cache.set(r.URL, r) }); }, - Request(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true, Seed:boolean = true) + Request(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true, Seed:boolean = true):FetchGuide { let check = Fetch.Cache.get(URL); @@ -188,17 +190,24 @@ export const Fetch = { 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}); + 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; + return [check, check.JSON ? true : false, true]; } else { @@ -206,38 +215,49 @@ export const Fetch = { let secondsAge = (new Date().getTime() - check.CachedAt)/1000; if(secondsAge > check.CacheFor) { + // cached but expired + // - listen + // - init console.log(`...outdated...`); - return load(check); + return [load(check), true, true]; } else { + // cached and ready + // - init console.log(`...retrieved!`); - return check; + return [check, true, false]; } } }, Use(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true, Seed:boolean = true) { - type FetchHookState = [Data:undefined|object, Updating:boolean]; - const [cacheGet, cacheSet] = React.useState([undefined, true] as FetchHookState); - if(Fetch.ServerBlocking && Fetch.ServerTouched && DelaySSR) + const [receipt, init, listen] = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR, Seed); + const initialState:FetchHookState = init ? [receipt.JSON, listen] : [undefined, true]; + const [cacheGet, cacheSet] = React.useState(initialState); + + if(Fetch.ServerBlocking && Fetch.ServerTouched && DelaySSR) // if server-side rendering { - const receipt = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR, Seed); - if(receipt.CachedAt === 0 && receipt.Promise)// request for something that hasnt been cached yet + if(listen) // if the request is pending { - Fetch.ServerBlocking.push(receipt.Promise); + 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(receipt.Seed) + else // if request is ready { - Fetch.ServerTouched.add(receipt); + receipt.Seed && Fetch.ServerTouched.add(receipt); // add record to client seed list (if specified in receipt.seed) + return [receipt.JSON, false] as FetchHookState; } - return [receipt.JSON, receipt.CachedAt === 0]; } + React.useEffect(()=> { - const receipt = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR); - receipt.Promise?.then(()=>cacheSet([receipt.JSON, receipt.CachedAt === 0])); + if(listen) + { + //const receipt = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR); + receipt.Promise?.then(()=>cacheSet([receipt.JSON, receipt.CachedAt === 0])); + } } , []); diff --git a/server.tsx b/server.tsx index 2aae3d6..7074a81 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; }; @@ -224,14 +226,18 @@ FileListen("${url.pathname}", reloadHandler);`; Iso.Fetch.ServerBlocking = []; Iso.Fetch.ServerTouched = new Set(); Iso.Fetch.ServerRemove = new Set(); - let bake = SSR(); + 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. - bake = SSR(); + app = ; + await Prepass(app) + bake = SSR(app); } const seed:Iso.FetchRecord[] = []; @@ -258,13 +264,13 @@ FileListen("${url.pathname}", reloadHandler);`;
${results.html}
`; From 8b21aba029efd8551adecae6ef5627eb2a10e5af Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Sat, 22 Apr 2023 13:48:32 -0400 Subject: [PATCH 8/9] fetch config --- example/app.tsx | 8 +++++++- example/deep/component.tsx | 4 ++-- lib/iso.tsx | 16 ++++++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/example/app.tsx b/example/app.tsx index c785b1f..263eb16 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -9,7 +9,13 @@ export default ()=> { return
-

Title!!

+ + +

Title!!

suspended:

Loading!
}> diff --git a/example/deep/component.tsx b/example/deep/component.tsx index a93ae9a..7fa1bf2 100644 --- a/example/deep/component.tsx +++ b/example/deep/component.tsx @@ -7,7 +7,7 @@ export default ()=> const [routeGet, routeSet] = Iso.Router.Consumer(); type CatFact = {fact:string, length:number}|undefined; - const [Data, Updating] = Iso.Fetch.Use(`https://catfact.ninja/fact`, undefined, 60, true, true, true); + const [Data, Updating] = Iso.Fetch.Use(`https://catfact.ninja/fact`); console.log("render!!") @@ -15,7 +15,7 @@ export default ()=> return
Component Route is: {routeGet.Path.toString()} - + a link

Data:{Data && (Data as CatFact)?.fact}

Status:{Updating?'loading':'done'}

diff --git a/lib/iso.tsx b/lib/iso.tsx index 2989821..1bb54ea 100644 --- a/lib/iso.tsx +++ b/lib/iso.tsx @@ -160,7 +160,8 @@ export const Switch =({children}:{children:Children})=> export const Case =({children, value}:{children:Children, value?:string, default?:true})=>null; export const useRouteVars =()=> React.useContext(SwitchContext).keys; -export type FetchRecord = {URL:string, CacheFor:number, CachedAt:number, CacheOnServer:boolean, Promise?:Promise, DelaySSR:boolean, Seed:boolean, Error?:string, JSON?:object}; +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 = { @@ -175,7 +176,8 @@ export const Fetch = { Fetch.Cache.set(r.URL, r) }); }, - Request(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true, Seed:boolean = true):FetchGuide + 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); @@ -183,7 +185,7 @@ export const Fetch = { { Fetch.Cache.set(URL, inCheck); inCheck.CachedAt = 0; - inCheck.Promise = fetch(URL, Init).then(resp=>resp.json()).then((json)=>{ + inCheck.Promise = fetch(URL, Init?Init:undefined).then(resp=>resp.json()).then((json)=>{ inCheck.JSON = json; inCheck.CachedAt = new Date().getTime(); console.log(`...cached!`); @@ -231,13 +233,15 @@ export const Fetch = { } }, - Use(URL:string, Init?:RequestInit, CacheFor:number = 60, CacheOnServer:boolean = true, DelaySSR:boolean = true, Seed:boolean = true) + + Use(URL:string, Init?:RequestInit|null, Options?:FetchCachOptions) { - const [receipt, init, listen] = Fetch.Request(URL, Init, CacheFor, CacheOnServer, DelaySSR, Seed); + 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 && DelaySSR) // if server-side rendering + if(Fetch.ServerBlocking && Fetch.ServerTouched && config.DelaySSR) // if server-side rendering { if(listen) // if the request is pending { From 4f594c21680fedb753dc8ab751928f429d0b38cc Mon Sep 17 00:00:00 2001 From: Seth Trowbridge Date: Sat, 22 Apr 2023 15:02:53 -0400 Subject: [PATCH 9/9] fix missing hmr wrapper --- example/app.tsx | 1 - server.tsx | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/app.tsx b/example/app.tsx index 263eb16..2f4abe9 100644 --- a/example/app.tsx +++ b/example/app.tsx @@ -12,7 +12,6 @@ export default ()=>

Title!!

diff --git a/server.tsx b/server.tsx index 7074a81..385f1ff 100644 --- a/server.tsx +++ b/server.tsx @@ -270,7 +270,8 @@ FileListen("${url.pathname}", reloadHandler);`; import {Router, Fetch} from "@eno/iso"; Twind.install(CSS); Fetch.Seed(${JSON.stringify(seed)}); - hydrate( H(Router.Provider, null, H(App)), document.querySelector("#app")); + const hmrWrap = H( ()=>H(App) ); + hydrate( H(Router.Provider, null, hmrWrap), document.querySelector("#app")); `;