Skip to content

Commit 159d204

Browse files
committed
Add Reveal SSR support: streaming coordination, collapsed fallbacks, nested composites
- Server-side Reveal component with RevealGroupContext for streaming SSR - Collapsed mode renders non-frontier fallbacks inside <template> tags - Nested Reveal registers as composite slot with parent group - createRevealOrder on server creates owner to match client hydration IDs - createLoadingBoundary integrates with reveal group for collapse tracking - Together mode correctly ignores collapsed flag Checkpoint before string SSR collapsed mode fix. Made-with: Cursor
1 parent 4a954e7 commit 159d204

21 files changed

Lines changed: 1675 additions & 52 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"solid-js": minor
3+
---
4+
5+
Add Reveal component SSR support with streaming fragment coordination, collapsed fallback mode, and nested composite slots

examples/ssr/shared/src/components/App.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const Home = lazy(() => import("./Home"), "./Home");
77
const Settings = lazy(() => import("./Settings"), "./Settings");
88
const Stream = lazy(() => import("./Stream"), "./Stream");
99
const ErrorStream = lazy(() => import("./ErrorStream"), "./ErrorStream");
10+
const RevealPage = lazy(() => import("./Reveal"), "./Reveal");
1011

1112
const App = RouteHOC(() => {
1213
const [location, { matches }] = useContext(RouterContext);
@@ -37,6 +38,9 @@ const App = RouteHOC(() => {
3738
<li class={{ selected: matches("error-stream") }}>
3839
<Link path="error-stream">Error Stream</Link>
3940
</li>
41+
<li class={{ selected: matches("reveal") }}>
42+
<Link path="reveal">Reveal</Link>
43+
</li>
4044
</ul>
4145
<div class={["tab", { pending: isPending(location) }]}>
4246
<Loading fallback={<span class="loader">Loading...</span>}>
@@ -56,6 +60,9 @@ const App = RouteHOC(() => {
5660
<Match when={matches("error-stream")}>
5761
<ErrorStream />
5862
</Match>
63+
<Match when={matches("reveal")}>
64+
<RevealPage />
65+
</Match>
5966
</Switch>
6067
</Loading>
6168
</div>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { createMemo, createSignal, Loading, Reveal, Show } from "solid-js";
2+
3+
function delayedValue(ms, value) {
4+
return new Promise(resolve => setTimeout(() => resolve(value), ms));
5+
}
6+
7+
function AsyncCard(props) {
8+
const value = createMemo(() =>
9+
delayedValue(props.delay, `${props.title} resolved in ${props.delay}ms`)
10+
);
11+
return (
12+
<Loading fallback={<div class="loader">{props.title} loading...</div>}>
13+
<div class="reveal-card">
14+
<strong>{props.title}</strong>
15+
<div>{value()}</div>
16+
</div>
17+
</Loading>
18+
);
19+
}
20+
21+
const RevealPage = () => {
22+
const [together, setTogether] = createSignal(false);
23+
const [collapsed, setCollapsed] = createSignal(true);
24+
const [seed, setSeed] = createSignal(1);
25+
26+
return (
27+
<>
28+
<h1>Reveal</h1>
29+
<p>
30+
Compare reveal ordering with different mode combinations and rerun the async boundaries to
31+
watch reveal behavior during SSR/hydration.
32+
</p>
33+
<p>
34+
<strong>Run:</strong> {seed()}
35+
</p>
36+
<div
37+
style={{ display: "flex", gap: "1rem", "align-items": "center", "margin-bottom": "1rem" }}
38+
>
39+
<label>
40+
<input
41+
type="checkbox"
42+
checked={together()}
43+
onInput={e => setTogether(e.currentTarget.checked)}
44+
/>{" "}
45+
together
46+
</label>
47+
<label>
48+
<input
49+
type="checkbox"
50+
checked={collapsed()}
51+
onInput={e => setCollapsed(e.currentTarget.checked)}
52+
/>{" "}
53+
collapsed
54+
</label>
55+
<button onClick={() => setSeed(s => s + 1)}>Restart run</button>
56+
</div>
57+
58+
<Show when={seed()} keyed>
59+
<h2>Primary Group</h2>
60+
<Reveal together={together()} collapsed={collapsed()}>
61+
<div class="reveal-grid">
62+
<AsyncCard title="A" delay={500} />
63+
<AsyncCard title="B" delay={1100} />
64+
<AsyncCard title="C" delay={1700} />
65+
</div>
66+
</Reveal>
67+
68+
<h2>Nested Group</h2>
69+
<Reveal together={together()} collapsed={collapsed()}>
70+
<div class="reveal-grid">
71+
<AsyncCard title="Outer-1" delay={700} />
72+
<Reveal together={together()} collapsed={collapsed()}>
73+
<div class="reveal-grid">
74+
<AsyncCard title="Inner-1" delay={900} />
75+
<AsyncCard title="Inner-2" delay={1300} />
76+
</div>
77+
</Reveal>
78+
<AsyncCard title="Outer-2" delay={1500} />
79+
</div>
80+
</Reveal>
81+
</Show>
82+
</>
83+
);
84+
};
85+
86+
export default RevealPage;

examples/ssr/shared/static/styles.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,21 @@ a.link {
5656
font-size: 16px;
5757
font-weight: 600;
5858
}
59+
.reveal-grid {
60+
display: grid;
61+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
62+
gap: 1rem;
63+
margin-bottom: 1.5rem;
64+
}
65+
.reveal-card {
66+
background: #f8f9fa;
67+
border: 1px solid #dee2e6;
68+
border-radius: 6px;
69+
padding: 1rem;
70+
transition: opacity 0.3s ease;
71+
}
72+
.reveal-card strong {
73+
display: block;
74+
margin-bottom: 0.5rem;
75+
color: #337ab7;
76+
}

examples/ssr/string/rollup.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ export default [
8989
],
9090
preserveEntrySignatures: false,
9191
plugins: [
92-
nodeResolve({ exportConditions: ["solid"] }),
92+
nodeResolve({ exportConditions: ["solid", "development"] }),
9393
babel({
9494
babelHelpers: "bundled",
95-
presets: [["solid", { generate: "dom", hydratable: true }]]
95+
presets: [["solid", { generate: "dom", hydratable: true, dev: true }]]
9696
}),
9797
common(),
9898
solidAssetManifest(),

packages/solid-h/jsx-runtime/src/jsx.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,12 +280,16 @@ export namespace JSX {
280280

281281
type BooleanAttribute = true | false | "";
282282

283+
type BooleanProperty = true | false;
284+
283285
type EnumeratedPseudoBoolean = "false" | "true";
284286

285287
type EnumeratedAcceptsEmpty = "" | true;
286288

287289
type RemoveAttribute = undefined | false;
288290

291+
type RemoveProperty = undefined;
292+
289293
// ARIA
290294

291295
// All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/
@@ -1523,6 +1527,7 @@ export namespace JSX {
15231527
autocomplete?: FunctionMaybe<HTMLAutocomplete | RemoveAttribute>;
15241528
capture?: FunctionMaybe<"user" | "environment" | RemoveAttribute>;
15251529
checked?: FunctionMaybe<BooleanAttribute | RemoveAttribute>;
1530+
"prop:checked"?: FunctionMaybe<BooleanProperty | RemoveProperty>;
15261531
colorspace?: FunctionMaybe<string | RemoveAttribute>;
15271532
dirname?: FunctionMaybe<string | RemoveAttribute>;
15281533
disabled?: FunctionMaybe<BooleanAttribute | RemoveAttribute>;
@@ -1578,6 +1583,7 @@ export namespace JSX {
15781583
| RemoveAttribute
15791584
>;
15801585
value?: FunctionMaybe<string | string[] | number | RemoveAttribute>;
1586+
"prop:value"?: FunctionMaybe<string | string[] | number | RemoveProperty>;
15811587
width?: FunctionMaybe<number | string | RemoveAttribute>;
15821588

15831589
/** @non-standard */
@@ -1658,6 +1664,7 @@ export namespace JSX {
16581664
disableremoteplayback?: FunctionMaybe<BooleanAttribute | RemoveAttribute>;
16591665
loop?: FunctionMaybe<BooleanAttribute | RemoveAttribute>;
16601666
muted?: FunctionMaybe<BooleanAttribute | RemoveAttribute>;
1667+
"prop:muted"?: FunctionMaybe<BooleanProperty | RemoveProperty>;
16611668
preload?: FunctionMaybe<
16621669
"none" | "metadata" | "auto" | EnumeratedAcceptsEmpty | RemoveAttribute
16631670
>;
@@ -1764,7 +1771,9 @@ export namespace JSX {
17641771
disabled?: FunctionMaybe<BooleanAttribute | RemoveAttribute>;
17651772
label?: FunctionMaybe<string | RemoveAttribute>;
17661773
selected?: FunctionMaybe<BooleanAttribute | RemoveAttribute>;
1774+
"prop:selected"?: FunctionMaybe<BooleanProperty | RemoveProperty>;
17671775
value?: FunctionMaybe<string | string[] | number | RemoveAttribute>;
1776+
"prop:value"?: FunctionMaybe<string | string[] | number | RemoveProperty>;
17681777
}
17691778
interface OutputHTMLAttributes<T> extends HTMLAttributes<T> {
17701779
for?: FunctionMaybe<string | RemoveAttribute>;
@@ -1819,6 +1828,7 @@ export namespace JSX {
18191828
required?: FunctionMaybe<BooleanAttribute | RemoveAttribute>;
18201829
size?: FunctionMaybe<number | string | RemoveAttribute>;
18211830
value?: FunctionMaybe<string | string[] | number | RemoveAttribute>;
1831+
"prop:value"?: FunctionMaybe<string | string[] | number | RemoveProperty>;
18221832
}
18231833
interface HTMLSlotElementAttributes<T> extends HTMLAttributes<T> {
18241834
name?: FunctionMaybe<string | RemoveAttribute>;
@@ -1893,6 +1903,7 @@ export namespace JSX {
18931903
required?: FunctionMaybe<BooleanAttribute | RemoveAttribute>;
18941904
rows?: FunctionMaybe<number | string | RemoveAttribute>;
18951905
value?: FunctionMaybe<string | string[] | number | RemoveAttribute>;
1906+
"prop:value"?: FunctionMaybe<string | string[] | number | RemoveProperty>;
18961907
wrap?: FunctionMaybe<"hard" | "soft" | "off" | RemoveAttribute>;
18971908
}
18981909
interface ThHTMLAttributes<T> extends HTMLAttributes<T> {

packages/solid-web/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export {
1616
For,
1717
Show,
1818
Loading,
19+
Reveal,
1920
Switch,
2021
Match,
2122
Repeat,

packages/solid-web/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export {
3838
Match,
3939
Errored,
4040
Loading,
41+
Reveal,
4142
NoHydration,
4243
Hydration,
4344
merge as mergeProps

0 commit comments

Comments
 (0)