issue/fetch #21
@ -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 <div class="p-4 font-sans">
|
||||
<Iso.Metas title="Main Page!"/>
|
||||
<h1 class="my-2 font(bold serif) text(2xl)">Title!!</h1>
|
||||
<h2>subtitle</h2>
|
||||
<Component/>
|
||||
<h2>suspended:</h2>
|
||||
<React.Suspense fallback={<div>Loading!</div>}>
|
||||
<Comp/>
|
||||
</React.Suspense>
|
||||
<Iso.Switch>
|
||||
<Iso.Case value="page">
|
||||
<Iso.Switch>
|
||||
|
@ -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 <div class="p-4 text-red-500">
|
||||
<Iso.Metas title="Component!"/>
|
||||
Route is: {routeGet.Path.toString()}
|
||||
Component Route is: {routeGet.Path.toString()}
|
||||
<button className="p-4 bg-red-500 text-white" onClick={e=>{countSet(countGet+1); routeSet(["lol", "idk"], {count:countGet+1});}}>{countGet}</button>
|
||||
Component!!!
|
||||
<a href="/page/about-us" className="p-2 text(lg blue-500) font-bold">a link</a>
|
||||
<p>Data:{Data && (Data as CatFact)?.fact}</p>
|
||||
<p>Status:{Updating?'loading':'done'}</p>
|
||||
|
56
lib/iso.tsx
56
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<FetchRecord>, 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<string, FetchRecord>,
|
||||
ServerBlocking:false as false|Promise<FetchRecord>[],
|
||||
@ -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]));
|
||||
}
|
||||
}
|
||||
, []);
|
||||
|
||||
|
16
server.tsx
16
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<string>=>
|
||||
{
|
||||
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(<Iso.Router.Provider url={url}><App/></Iso.Router.Provider>);
|
||||
let app = <Iso.Router.Provider url={url}><App/></Iso.Router.Provider>;
|
||||
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(<Iso.Router.Provider url={url}><App/></Iso.Router.Provider>);
|
||||
app = <Iso.Router.Provider url={url}><App/></Iso.Router.Provider>;
|
||||
await Prepass(app)
|
||||
bake = SSR(app);
|
||||
}
|
||||
|
||||
const seed:Iso.FetchRecord[] = [];
|
||||
@ -258,13 +264,13 @@ FileListen("${url.pathname}", reloadHandler);`;
|
||||
<body>
|
||||
<div id="app">${results.html}</div>
|
||||
<script type="module">
|
||||
import {render, 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 {default as App, CSS} from "@eno/app";
|
||||
import {Router, Fetch} from "@eno/iso";
|
||||
Twind.install(CSS);
|
||||
Fetch.Seed(${JSON.stringify(seed)});
|
||||
render( H(Router.Provider, null, H(App)), document.querySelector("#app"));
|
||||
hydrate( H(Router.Provider, null, H(App)), document.querySelector("#app"));
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
Loading…
Reference in New Issue
Block a user