diff --git a/deno.json b/deno.json
index 194d62d32..22179c096 100644
--- a/deno.json
+++ b/deno.json
@@ -9,7 +9,7 @@
"std/": "https://deno.land/std@0.204.0/",
"partytown/": "https://deno.land/x/partytown@0.4.8/",
"deco-sites/std/": "https://denopkg.com/deco-sites/std@1.26.7/",
- "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": {
diff --git a/htmx/sections/Deferred.tsx b/htmx/sections/Deferred.tsx
index ce6379477..0c3401765 100644
--- a/htmx/sections/Deferred.tsx
+++ b/htmx/sections/Deferred.tsx
@@ -2,7 +2,7 @@ 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
@@ -10,7 +10,8 @@ import { renderSection, shouldForceRender } from "../../utils/deferred.tsx";
*/
interface Load {
type: "load";
- /** @hide true */
+
+ // TODO: @gimenes add delay
delay?: number;
}
@@ -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 }) => )}
>
);
}
@@ -61,16 +61,15 @@ const Deferred = (props: Props) => {
}
return (
- <>
-
- {props.fallbacks?.map(renderSection)}
- >
+
+ {fallback && }
+
);
};
diff --git a/utils/deferred.tsx b/utils/deferred.ts
similarity index 51%
rename from utils/deferred.tsx
rename to utils/deferred.ts
index dd10cb224..2a555cfb6 100644
--- a/utils/deferred.tsx
+++ b/utils/deferred.ts
@@ -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, searchParams }: { ctx: Ctx; searchParams: URLSearchParams },
): boolean => ctx.isBot || searchParams.get(__DECO_FBT) === "0";
-
-export const renderSection = ({ Component, props }: Section) => (
-
-);
diff --git a/website/handlers/fresh.ts b/website/handlers/fresh.ts
index e6f2f89e0..8be8814cb 100644
--- a/website/handlers/fresh.ts
+++ b/website/handlers/fresh.ts
@@ -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
*/
diff --git a/website/manifest.gen.ts b/website/manifest.gen.ts
index bc05df6cd..0432f8631 100644
--- a/website/manifest.gen.ts
+++ b/website/manifest.gen.ts
@@ -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": {
@@ -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,
diff --git a/website/mod.ts b/website/mod.ts
index a7985fbd3..73a9cb542 100644
--- a/website/mod.ts
+++ b/website/mod.ts
@@ -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;
diff --git a/website/sections/Rendering/Deferred.tsx b/website/sections/Rendering/Deferred.tsx
index 71e4a8b67..fb55b2d9f 100644
--- a/website/sections/Rendering/Deferred.tsx
+++ b/website/sections/Rendering/Deferred.tsx
@@ -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 {
@@ -43,8 +43,6 @@ export interface Props {
sections: Section[];
display?: boolean;
behavior?: Scroll | Intersection | Load;
- /** @hide true */
- fallbacks?: Section[];
}
const script = (
@@ -103,7 +101,7 @@ const Deferred = (props: Props) => {
if (display) {
return (
<>
- {sections.map(renderSection)}
+ {sections.map(({ Component, props }) => )}
>
);
}
@@ -125,7 +123,6 @@ const Deferred = (props: Props) => {
behavior?.payload.toString() || "",
)}
/>
- {props.fallbacks?.map(renderSection)}
>
);
};
diff --git a/website/sections/Rendering/Lazy.tsx b/website/sections/Rendering/Lazy.tsx
new file mode 100644
index 000000000..b2d35559b
--- /dev/null
+++ b/website/sections/Rendering/Lazy.tsx
@@ -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) => () => (
+
+ {!context.isDeploy && (
+ <>
+
+ Async Rendering not implemented for section{" "}
+ {section}
+
+
+ If you are a developer, export a{" "}
+
LoadingFallback{" "}
+ component in this section. To learn more, check out our{" "}
+
+ guide
+
+
+
+ If you are NOT a developer, you can tweak Async Rendering. To learn
+ more, check out our{" "}
+
+ blog post
+
+
+ >
+ )}
+
+);
+
+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),
+ };
+ }
+
+ const resolvingMatchers: Record = {};
+ const resolvedSection = await ctx.get(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>;
+
+function Lazy({ section, loading }: SectionProps) {
+ const ctx = useSectionContext();
+
+ if (!ctx) {
+ throw new Error("Missing SectionContext");
+ }
+
+ if (loading === "lazy") {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+}
+
+export const onBeforeResolveProps = (props: Props) => ({
+ ...props,
+ section: asResolved(props.section),
+});
+
+export default Lazy;
diff --git a/website/sections/Rendering/SingleDeferred.tsx b/website/sections/Rendering/SingleDeferred.tsx
index 399e574af..94dc9152f 100644
--- a/website/sections/Rendering/SingleDeferred.tsx
+++ b/website/sections/Rendering/SingleDeferred.tsx
@@ -1,107 +1,2 @@
-import type { Section } from "deco/blocks/section.ts";
-import { SectionContext } from "deco/components/section.tsx";
-import { RequestContext } from "deco/deco.ts";
-import { asResolved, isDeferred } from "deco/mod.ts";
-import { useContext } from "preact/hooks";
-import HTMXDeferred from "../../../htmx/sections/Deferred.tsx";
-import { shouldForceRender } from "../../../utils/deferred.tsx";
-import type { AppContext } from "../../mod.ts";
-import PartialDeferred from "./Deferred.tsx";
-
-const isHtmx = (
- props: SectionProps,
-): props is SectionProps & { framework: "htmx" } => props.framework === "htmx";
-
-interface Props {
- /** @label hidden */
- section: Section;
- /**
- * @description fresh/Deferred.tsx props
- * @hide true
- */
- display?: boolean;
- /**
- * @description htmx/Deferred.tsx prop
- * @hide true
- */
- loading?: "eager";
-}
-
-export const loader = async (
- props: Props,
- _req: Request,
- ctx: AppContext,
-) => {
- const isFreshSyncRender = "display" in props && props.display === true;
- const isHtmxSyncRender = "loading" in props && props.loading === "eager";
- const url = new URL(_req.url);
- const _shouldForceRender = shouldForceRender({
- ctx,
- searchParams: url.searchParams,
- });
- const shouldRender = isFreshSyncRender || isHtmxSyncRender ||
- _shouldForceRender;
- const framework = ctx.flavor?.framework || "fresh";
-
- if (shouldRender) {
- const section = isDeferred(props.section)
- ? await props.section()
- : props.section;
- return { ...props, framework, section, fallback: null };
- }
-
- // How to improve it
- const abortControler = new AbortController();
- abortControler.abort();
- const section = isDeferred(props.section)
- ? await RequestContext.bind(
- { signal: abortControler.signal },
- props.section,
- )()
- : props.section;
-
- return { ...props, framework, section: null, fallback: section };
-};
-
-type SectionProps = Awaited>;
-
-function SingleDeferred(props: SectionProps) {
- const ctx = useContext(SectionContext)!;
- const idxOrNaN = Number(ctx?.resolveChain.at(-2)?.value);
- const delay = 25 * (Math.min(Number.isNaN(idxOrNaN) ? 1 : idxOrNaN, 10) || 1);
- const sections = props.section ? [props.section] : [];
- const fallbacks = props.fallback ? [props.fallback] : undefined;
-
- return (
-
- {isHtmx(props)
- ? (
-
- )
- : (
-
- )}
-
- );
-}
-
-export const onBeforeResolveProps = (props: Props) => {
- return {
- ...props,
- section: asResolved(props.section, true),
- };
-};
-
-export default SingleDeferred;
+export * from "./Lazy.tsx";
+export { default } from "./Lazy.tsx";