authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-12-12 03:17:53-08:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-01-14 01:08:14-08:00
log869abdb2ce6c725d963e91c254dd015650630ef5
treefb77fb071d6d56072beb2c9657085a14d50a0d12
parentd1a62aed37dbf7c17fde800dc898d7b41ab56481
signature Commit is signed but in an unrecognized format.

chore: delete unused files

suspense is moved to `kolmxrtq`, to be revived later

4 files changed, 0 insertions(+), 166 deletions(-)

framework/test-fixture/Component.marko deleted-4
......@@ -1,4 +0,0 @@
1<div meow=null />
2<div>
3 wait(${null})
4</div>
framework/test-fixture/UseComponent.marko deleted-6
......@@ -1,6 +0,0 @@
1import Component from './Component.marko';
2
3<h1>web page</h1>
4<if=!false>
5 <Component=null/>
6</>
suspense.test.tsx deleted-40
......@@ -1,40 +0,0 @@
1import { test } from "node:test";
2import { renderStreaming, Suspense } from "./suspense.ts";
3
4test("sanity", async (t) => {
5 let resolve: () => void = null!;
6
7 // @ts-expect-error
8 async function AsyncComponent() {
9 await new Promise<void>((done) => resolve = done);
10 return <button>wow!</button>;
11 }
12
13 const example = (
14 <main>
15 <h1>app shell</h1>
16 <Suspense fallback="loading...">
17 <AsyncComponent />
18 </Suspense>
19 <footer>(c) 2025</footer>
20 </main>
21 );
22
23 const iterator = renderStreaming(example);
24 const assertContinue = (actual: unknown, value: unknown) =>
25 t.assert.deepEqual(actual, { done: false, value });
26
27 assertContinue(
28 await iterator.next(),
29 "<template shadowrootmode=open><main><h1>app shell</h1><slot name=suspended_1>loading...</slot><footer>(c) 2025</footer></main></template>",
30 );
31 t.assert.ok(resolve !== null), resolve();
32 assertContinue(
33 await iterator.next(),
34 "<button slot=suspended_1>wow!</button>",
35 );
36 t.assert.deepEqual(
37 await iterator.next(),
38 { done: true, value: {} },
39 );
40});
suspense.ts deleted-116
......@@ -1,116 +0,0 @@
1// TODO: revive the suspense renderer
2/**
3 * This file implements out-of-order HTML streaming, mimicking the React
4 * Suspense API. To use, place Suspense around an expensive async component
5 * and render the page with 'renderStreaming'.
6 *
7 * Implementation of this article:
8 * https://lamplightdev.com/blog/2024/01/10/streaming-html-out-of-order-without-javascript/
9 *
10 * I would link to an article from Next.js or React, but their examples
11 * are too verbose and not informative to what they actually do.
12 * @module
13 */
14const userData = render.userData<null | State>(() => null);
15
16export interface Input {
17 children: render.Node;
18 fallback?: render.Node;
19}
20
21/**
22 * When used in combination with `Suspense.renderStreaming`, this component
23 * displays `fallback` until the async `children` `Node` has resolved..
24 *
25 * return <div>
26 * <h1>page shell</h1>
27 * <Suspense fallback="Loading...">
28 * <ComponentWithAwait />
29 * </Suspense>
30 * </div>;
31 */
32export function Suspense({ children, fallback }: Input): render.Node {
33 const state = userData.get();
34 if (!state) return children;
35 if (state.nested) throw new Error("<Suspense> cannot be nested");
36 const parent = UNWRAP(render.current);
37 const r = render.init(true, { [userData.key]: { nested: true } });
38 const resolved = render.resolveNode(r, children);
39 if (r.async == 0) return render.raw(resolved);
40 const name = "suspended_" + ++state.nextId;
41 state.nested = true;
42 const ip: [render.ResolvedNode] = [
43 render.resolvedElement(
44 "slot",
45 { name },
46 fallback ? render.resolveNode(parent, fallback) : "",
47 ),
48 ];
49 state.nested = false;
50 r.asyncDone = () => {
51 const rejections = r.rejections;
52 if (rejections && rejections.length > 0) throw new Error("TODO");
53 state.pushChunk?.(name, ip[0] = resolved);
54 };
55 return render.raw(ip);
56}
57
58// TODO: add a User-Agent parameter, which is used to determine if a
59// fallback path must be used.
60// - Before ~2024 needs to use a JS implementation.
61// - IE should probably bail out entirely.
62export async function* renderStreaming<
63 T extends render.Addons = Record<never, unknown>,
64>(node: render.Node, addon: T = {} as T) {
65 const {
66 text: begin,
67 addon: { [userData.key]: state, ...addonOutput },
68 } = await render.async(node, {
69 ...addon,
70 [userData.key]: {
71 nested: false,
72 nextId: 0,
73 completed: 0,
74 pushChunk: () => {},
75 } satisfies State as State,
76 });
77 if (state.nextId === 0) {
78 yield begin;
79 return addonOutput as unknown as T;
80 }
81 let resolve: (() => void) | null = null;
82 let chunks: string[] = [];
83 state.pushChunk = (slot, node) => {
84 while (node.length === 1 && Array.isArray(node)) node = UNWRAP(node[0]);
85 if (node[0] === render.kElement) {
86 (node as render.ResolvedElement)[2].slot = slot;
87 } else {
88 node = render.resolvedElement("clover-suspense", {
89 style: "display:contents",
90 slot,
91 }, node);
92 }
93 chunks.push(render.stringifyNode(node));
94 resolve?.();
95 };
96 yield `<template shadowrootmode=open>${begin}</template>`;
97 do {
98 await new Promise<void>((done) => (resolve = done));
99 yield* chunks;
100 chunks = [];
101 } while (state!.nextId < state!.completed);
102 return addonOutput as unknown as T;
103}
104
105interface State {
106 nested: boolean;
107 nextId: number;
108 completed: number;
109 pushChunk(name: string, node: render.ResolvedNode): void;
110}
111
112export declare namespace Suspense {
113 export type { Input };
114}
115
116import * as render from "lib/render.ts";