Skip to content

Commit

Permalink
async rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes committed Jul 24, 2024
1 parent 6564ec8 commit b5b19fe
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 145 deletions.
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"std/": "https://deno.land/[email protected]/",
"partytown/": "https://deno.land/x/[email protected]/",
"deco-sites/std/": "https://denopkg.com/deco-sites/[email protected]/",
"deco/": "https://cdn.jsdelivr.net/gh/deco-cx/deco@1.78.0/"
"deco/": "https://cdn.jsdelivr.net/gh/deco-cx/deco@30aa5339af59f53d5452b29eebde196a1df66a16/"
},
"lock": false,
"tasks": {
Expand Down
31 changes: 15 additions & 16 deletions htmx/sections/Deferred.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import type { Section } from "deco/blocks/section.ts";
import { useSection } from "deco/hooks/useSection.ts";
import { asResolved, isDeferred } from "deco/mod.ts";
import { AppContext } from "../mod.ts";
import { renderSection, shouldForceRender } from "../../utils/deferred.tsx";
import { shouldForceRender } from "../../utils/deferred.ts";

/**
* @titleBy type
* @description fires once when the element is first loaded
*/
interface Load {
type: "load";
/** @hide true */

// TODO: @gimenes add delay
delay?: number;
}

Expand All @@ -34,19 +35,18 @@ interface Intersect {

export interface Props {
sections: Section[];
/** @hide true */
fallbacks?: Section[];
fallback?: Section;
trigger?: Load | Revealed | Intersect;
loading?: "lazy" | "eager";
}

const Deferred = (props: Props) => {
const { sections, loading, trigger } = props;
const { sections, loading, trigger, fallback } = props;

if (loading === "eager") {
return (
<>
{sections.map(renderSection)}
{sections.map(({ Component, props }) => <Component {...props} />)}
</>
);
}
Expand All @@ -61,16 +61,15 @@ const Deferred = (props: Props) => {
}

return (
<>
<div
hx-get={href}
hx-trigger={triggerList.join(" ")}
hx-target="closest section"
hx-swap="outerHTML"
style={{ height: "100vh" }}
/>
{props.fallbacks?.map(renderSection)}
</>
<div
hx-get={href}
hx-trigger={`${trigger?.type ?? "load"} once`}
hx-target="closest section"
hx-swap="outerHTML"
style={{ height: "100vh" }}
>
{fallback && <fallback.Component {...fallback.props} />}
</div>
);
};

Expand Down
7 changes: 1 addition & 6 deletions utils/deferred.tsx → utils/deferred.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { Section } from "deco/mod.ts";
import { __DECO_FBT } from "../website/handlers/fresh.ts";
export const __DECO_FBT = "__decoFBT";

export const shouldForceRender = <Ctx extends { isBot?: boolean }>(
{ ctx, searchParams }: { ctx: Ctx; searchParams: URLSearchParams },
): boolean => ctx.isBot || searchParams.get(__DECO_FBT) === "0";

export const renderSection = ({ Component, props }: Section) => (
<Component {...props} />
);
3 changes: 1 addition & 2 deletions website/handlers/fresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import { DecoState } from "deco/types.ts";
import { allowCorsFor } from "deco/utils/http.ts";
import { getSetCookies } from "std/http/cookie.ts";
import { ConnInfo } from "std/http/server.ts";
import { __DECO_FBT } from "../../utils/deferred.ts";
import { AppContext } from "../mod.ts";

export const __DECO_FBT = "__decoFBT";

/**
* @title Fresh Config
*/
Expand Down
14 changes: 8 additions & 6 deletions website/manifest.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ import * as $$$$$$$14 from "./matchers/userAgent.ts";
import * as $$$$$0 from "./pages/Page.tsx";
import * as $$$$$$0 from "./sections/Analytics/Analytics.tsx";
import * as $$$$$$1 from "./sections/Rendering/Deferred.tsx";
import * as $$$$$$2 from "./sections/Rendering/SingleDeferred.tsx";
import * as $$$$$$3 from "./sections/Seo/Seo.tsx";
import * as $$$$$$4 from "./sections/Seo/SeoV2.tsx";
import * as $$$$$$2 from "./sections/Rendering/Lazy.tsx";
import * as $$$$$$3 from "./sections/Rendering/SingleDeferred.tsx";
import * as $$$$$$4 from "./sections/Seo/Seo.tsx";
import * as $$$$$$5 from "./sections/Seo/SeoV2.tsx";

const manifest = {
"functions": {
Expand Down Expand Up @@ -84,9 +85,10 @@ const manifest = {
"sections": {
"website/sections/Analytics/Analytics.tsx": $$$$$$0,
"website/sections/Rendering/Deferred.tsx": $$$$$$1,
"website/sections/Rendering/SingleDeferred.tsx": $$$$$$2,
"website/sections/Seo/Seo.tsx": $$$$$$3,
"website/sections/Seo/SeoV2.tsx": $$$$$$4,
"website/sections/Rendering/Lazy.tsx": $$$$$$2,
"website/sections/Rendering/SingleDeferred.tsx": $$$$$$3,
"website/sections/Seo/Seo.tsx": $$$$$$4,
"website/sections/Seo/SeoV2.tsx": $$$$$$5,
},
"matchers": {
"website/matchers/always.ts": $$$$$$$0,
Expand Down
5 changes: 3 additions & 2 deletions website/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ export interface Props {
caching?: Caching;

/**
* @title Async Rendering
* @description Async sections will be deferred to the client-side
* @title Global Async Rendering (Deprecated)
* @description Please disable this setting and enable each section individually. More info at https://deco.cx/en/blog/async-render-default
* @deprecated true
* @default false
*/
firstByteThresholdMS?: boolean;
Expand Down
7 changes: 2 additions & 5 deletions website/sections/Rendering/Deferred.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useScriptAsDataURI } from "deco/hooks/useScript.ts";
import { asResolved, isDeferred } from "deco/mod.ts";
import { useId } from "preact/hooks";
import { AppContext } from "../../mod.ts";
import { renderSection, shouldForceRender } from "../../../utils/deferred.tsx";
import { shouldForceRender } from "../../../utils/deferred.ts";

/** @titleBy type */
export interface Scroll {
Expand Down Expand Up @@ -43,8 +43,6 @@ export interface Props {
sections: Section[];
display?: boolean;
behavior?: Scroll | Intersection | Load;
/** @hide true */
fallbacks?: Section[];
}

const script = (
Expand Down Expand Up @@ -103,7 +101,7 @@ const Deferred = (props: Props) => {
if (display) {
return (
<>
{sections.map(renderSection)}
{sections.map(({ Component, props }) => <Component {...props} />)}
</>
);
}
Expand All @@ -125,7 +123,6 @@ const Deferred = (props: Props) => {
behavior?.payload.toString() || "",
)}
/>
{props.fallbacks?.map(renderSection)}
</>
);
};
Expand Down
145 changes: 145 additions & 0 deletions website/sections/Rendering/Lazy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import type { Section } from "deco/blocks/section.ts";
import { SectionContext } from "deco/components/section.tsx";
import { asResolved, context } from "deco/mod.ts";
import { useContext } from "preact/hooks";
import { shouldForceRender } from "../../../utils/deferred.ts";
import type { AppContext } from "../../mod.ts";

const useSectionContext = () => useContext(SectionContext);

interface Props {
/** @label hidden */
section: Section;

/**
* @description htmx/Deferred.tsx prop
* @hide true
*/
loading?: "eager" | "lazy";
}

const defaultFallbackFor = (section: string) => () => (
<div
style={{
height: "50vh",
width: "100%",
display: "flex",
flexDirection: "column",
gap: "0.5rem",
justifyContent: "center",
alignItems: "center",
textAlign: "center",
}}
>
{!context.isDeploy && (
<>
<div>
Async Rendering not implemented for section{" "}
<span style={{ fontSize: "1rem", fontWeight: 600 }}>{section}</span>
</div>
<div style={{ fontSize: "0.75rem" }}>
If you are a developer, export a{" "}
<span style={{ fontWeight: 600 }}>LoadingFallback</span>{" "}
component in this section. To learn more, check out our{" "}
<a
style={{ fontWeight: 600, textDecoration: "underline" }}
href="https://deco.cx/en/blog/async-rendering#:~:text=Customizing%20Loading%20States%20Made%20Easy%3A"
>
guide
</a>
</div>
<div style={{ fontSize: "0.75rem" }}>
If you are NOT a developer, you can tweak Async Rendering. To learn
more, check out our{" "}
<a
style={{ fontWeight: 600, textDecoration: "underline" }}
href="https://deco.cx/en/blog/async-render-default#:~:text=Q%3A%20Can%20I%20disable%20the%20async%20render%3F"
>
blog post
</a>
</div>
</>
)}
</div>
);

export const loader = async (props: Props, req: Request, ctx: AppContext) => {
const { section, loading } = props;
const url = new URL(req.url);

const shouldRender = loading === "eager" || shouldForceRender({
ctx,
searchParams: url.searchParams,
});

if (shouldRender) {
return {
loading: "eager",
section: await ctx.get<Section>(section),
};
}

const resolvingMatchers: Record<string, boolean> = {};
const resolvedSection = await ctx.get<Section>(section, {
propagateOptions: true,
hooks: {
onPropsResolveStart: (
resolve,
_props,
resolver,
_resolveType,
ctx,
) => {
if (resolvingMatchers[ctx.resolveId]) {
return resolve();
}
if (resolver?.type === "matchers") { // matchers should not have a timeout.
const id = crypto.randomUUID();
resolvingMatchers[id] = true;
return resolve(id);
}
if (resolver?.type === "loaders") {
// deno-lint-ignore no-explicit-any
return undefined as any;
}
return resolve();
},
},
});

return {
loading: shouldRender ? "eager" : "lazy",
section: {
Component: resolvedSection.LoadingFallback ??
defaultFallbackFor(resolvedSection.metadata?.component ?? "unknown"),
props: {},
},
};
};

type SectionProps = Awaited<ReturnType<typeof loader>>;

function Lazy({ section, loading }: SectionProps) {
const ctx = useSectionContext();

if (!ctx) {
throw new Error("Missing SectionContext");
}

if (loading === "lazy") {
return (
<ctx.FallbackWrapper props={{ loading: "eager" }}>
<section.Component {...section.props} />
</ctx.FallbackWrapper>
);
}

return <section.Component {...section.props} />;
}

export const onBeforeResolveProps = (props: Props) => ({
...props,
section: asResolved(props.section),
});

export default Lazy;
Loading

0 comments on commit b5b19fe

Please sign in to comment.