fibrae 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components.d.ts +40 -0
- package/dist/components.js +63 -0
- package/dist/components.js.map +1 -0
- package/dist/core.d.ts +25 -0
- package/dist/core.js +46 -0
- package/dist/core.js.map +1 -0
- package/dist/dom.d.ts +16 -0
- package/dist/dom.js +67 -0
- package/dist/dom.js.map +1 -0
- package/dist/fiber-render.d.ts +33 -0
- package/dist/fiber-render.js +1069 -0
- package/dist/fiber-render.js.map +1 -0
- package/dist/h.d.ts +19 -0
- package/dist/h.js +26 -0
- package/dist/h.js.map +1 -0
- package/dist/hydration.d.ts +30 -0
- package/dist/hydration.js +375 -0
- package/dist/hydration.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-runtime/index.d.ts +29 -0
- package/dist/jsx-runtime/index.js +61 -0
- package/dist/jsx-runtime/index.js.map +1 -0
- package/dist/render.d.ts +19 -0
- package/dist/render.js +325 -0
- package/dist/render.js.map +1 -0
- package/dist/router/History.d.ts +129 -0
- package/dist/router/History.js +241 -0
- package/dist/router/History.js.map +1 -0
- package/dist/router/Link.d.ts +52 -0
- package/dist/router/Link.js +131 -0
- package/dist/router/Link.js.map +1 -0
- package/dist/router/Navigator.d.ts +108 -0
- package/dist/router/Navigator.js +225 -0
- package/dist/router/Navigator.js.map +1 -0
- package/dist/router/Route.d.ts +65 -0
- package/dist/router/Route.js +143 -0
- package/dist/router/Route.js.map +1 -0
- package/dist/router/Router.d.ts +167 -0
- package/dist/router/Router.js +328 -0
- package/dist/router/Router.js.map +1 -0
- package/dist/router/RouterBuilder.d.ts +128 -0
- package/dist/router/RouterBuilder.js +112 -0
- package/dist/router/RouterBuilder.js.map +1 -0
- package/dist/router/RouterOutlet.d.ts +57 -0
- package/dist/router/RouterOutlet.js +132 -0
- package/dist/router/RouterOutlet.js.map +1 -0
- package/dist/router/RouterState.d.ts +102 -0
- package/dist/router/RouterState.js +94 -0
- package/dist/router/RouterState.js.map +1 -0
- package/dist/router/index.d.ts +28 -0
- package/dist/router/index.js +31 -0
- package/dist/router/index.js.map +1 -0
- package/dist/runtime.d.ts +55 -0
- package/dist/runtime.js +68 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scope-utils.d.ts +14 -0
- package/dist/scope-utils.js +29 -0
- package/dist/scope-utils.js.map +1 -0
- package/dist/server.d.ts +112 -0
- package/dist/server.js +313 -0
- package/dist/server.js.map +1 -0
- package/dist/shared.d.ts +136 -0
- package/dist/shared.js +53 -0
- package/dist/shared.js.map +1 -0
- package/dist/tracking.d.ts +23 -0
- package/dist/tracking.js +53 -0
- package/dist/tracking.js.map +1 -0
- package/package.json +62 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side rendering for Fibrae
|
|
3
|
+
*
|
|
4
|
+
* Renders VElement trees to HTML strings for SSR.
|
|
5
|
+
* Integrates with @effect-atom/atom's Hydration module for state serialization.
|
|
6
|
+
*
|
|
7
|
+
* Key design decisions (see docs/ssr-hydration-design.md):
|
|
8
|
+
* - Streams restart on client (no server continuation)
|
|
9
|
+
* - Atoms must use Atom.serializable() for state transfer
|
|
10
|
+
* - Same components work on server and client
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import { Atom, Registry as AtomRegistry, Hydration } from "@effect-atom/atom";
|
|
14
|
+
import { type VElement } from "./shared.js";
|
|
15
|
+
/**
|
|
16
|
+
* Result of renderToString - HTML plus serialized state
|
|
17
|
+
*
|
|
18
|
+
* The dehydratedState can be serialized and embedded in your HTML however
|
|
19
|
+
* you prefer (script tag, data attribute, separate endpoint, etc.).
|
|
20
|
+
* On the client, pass this state to render() via the initialState option.
|
|
21
|
+
*/
|
|
22
|
+
export interface RenderResult {
|
|
23
|
+
readonly html: string;
|
|
24
|
+
readonly dehydratedState: ReadonlyArray<Hydration.DehydratedAtom>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Options for SSR rendering
|
|
28
|
+
*/
|
|
29
|
+
export interface RenderOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Initial atom values to use during rendering.
|
|
32
|
+
* These will be set on the registry before rendering begins.
|
|
33
|
+
*/
|
|
34
|
+
readonly initialValues?: Iterable<readonly [Atom.Atom<unknown>, unknown]>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create a synchronous AtomRegistry layer for SSR.
|
|
38
|
+
* Uses synchronous task scheduling since we're not in a browser.
|
|
39
|
+
*/
|
|
40
|
+
declare const SSRAtomRegistryLayer: import("effect/Layer").Layer<AtomRegistry.AtomRegistry, never, never>;
|
|
41
|
+
/**
|
|
42
|
+
* Exported for SSR scenarios that need to compose with other layers.
|
|
43
|
+
* Use this when you need to provide additional services (e.g., Navigator, RouterHandlers)
|
|
44
|
+
* alongside the AtomRegistry.
|
|
45
|
+
*/
|
|
46
|
+
export { SSRAtomRegistryLayer };
|
|
47
|
+
/**
|
|
48
|
+
* Render a VElement tree to an HTML string with serialized state.
|
|
49
|
+
*
|
|
50
|
+
* Returns the HTML and the dehydrated atom state array. You can serialize
|
|
51
|
+
* and embed the state in your HTML however you prefer (script tag, data
|
|
52
|
+
* attribute, separate endpoint, etc.).
|
|
53
|
+
*
|
|
54
|
+
* On the client, pass this state to render() via the initialState option
|
|
55
|
+
* to hydrate atom values before DOM hydration.
|
|
56
|
+
*
|
|
57
|
+
* Note: Atoms must use `Atom.serializable({ key, schema })` to be included
|
|
58
|
+
* in the dehydrated state.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* import { renderToString } from "fibrae/server";
|
|
63
|
+
*
|
|
64
|
+
* const program = Effect.gen(function* () {
|
|
65
|
+
* const { html, dehydratedState } = yield* renderToString(<App />);
|
|
66
|
+
*
|
|
67
|
+
* // Embed state however you prefer
|
|
68
|
+
* const page = `
|
|
69
|
+
* <!DOCTYPE html>
|
|
70
|
+
* <html>
|
|
71
|
+
* <body>
|
|
72
|
+
* <div id="root">${html}</div>
|
|
73
|
+
* <script>window.__STATE__ = ${JSON.stringify(dehydratedState)}</script>
|
|
74
|
+
* <script src="/client.js"></script>
|
|
75
|
+
* </body>
|
|
76
|
+
* </html>
|
|
77
|
+
* `;
|
|
78
|
+
* return page;
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare const renderToString: (element: VElement, _options?: RenderOptions) => Effect.Effect<RenderResult, unknown, never>;
|
|
83
|
+
/**
|
|
84
|
+
* Render a VElement tree to HTML, requiring AtomRegistry and any other
|
|
85
|
+
* services the component tree needs.
|
|
86
|
+
*
|
|
87
|
+
* Use this when your components require additional services (Navigator, RouterHandlers, etc.)
|
|
88
|
+
* that you want to provide yourself.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* import { renderToStringWith, SSRAtomRegistryLayer } from "fibrae/server";
|
|
93
|
+
*
|
|
94
|
+
* const program = Effect.gen(function* () {
|
|
95
|
+
* const { html, dehydratedState } = yield* renderToStringWith(<App />);
|
|
96
|
+
* return { html, dehydratedState };
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* // Run with composed layers
|
|
100
|
+
* const result = Effect.runPromise(
|
|
101
|
+
* program.pipe(
|
|
102
|
+
* Effect.provide(Layer.mergeAll(
|
|
103
|
+
* SSRAtomRegistryLayer,
|
|
104
|
+
* navigatorLayer,
|
|
105
|
+
* routerHandlersLayer
|
|
106
|
+
* ))
|
|
107
|
+
* )
|
|
108
|
+
* );
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export declare const renderToStringWith: <R>(element: VElement, _options?: RenderOptions) => Effect.Effect<RenderResult, unknown, AtomRegistry.AtomRegistry | R>;
|
|
112
|
+
export { Hydration };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side rendering for Fibrae
|
|
3
|
+
*
|
|
4
|
+
* Renders VElement trees to HTML strings for SSR.
|
|
5
|
+
* Integrates with @effect-atom/atom's Hydration module for state serialization.
|
|
6
|
+
*
|
|
7
|
+
* Key design decisions (see docs/ssr-hydration-design.md):
|
|
8
|
+
* - Streams restart on client (no server continuation)
|
|
9
|
+
* - Atoms must use Atom.serializable() for state transfer
|
|
10
|
+
* - Same components work on server and client
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import * as Stream from "effect/Stream";
|
|
14
|
+
import * as Option from "effect/Option";
|
|
15
|
+
import * as Deferred from "effect/Deferred";
|
|
16
|
+
import { Atom, Registry as AtomRegistry, Hydration, } from "@effect-atom/atom";
|
|
17
|
+
import { isStream, isProperty, } from "./shared.js";
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// SSR Registry Layer
|
|
20
|
+
// =============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Create a synchronous AtomRegistry layer for SSR.
|
|
23
|
+
* Uses synchronous task scheduling since we're not in a browser.
|
|
24
|
+
*/
|
|
25
|
+
const SSRAtomRegistryLayer = AtomRegistry.layerOptions({
|
|
26
|
+
scheduleTask: (f) => f(),
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Exported for SSR scenarios that need to compose with other layers.
|
|
30
|
+
* Use this when you need to provide additional services (e.g., Navigator, RouterHandlers)
|
|
31
|
+
* alongside the AtomRegistry.
|
|
32
|
+
*/
|
|
33
|
+
export { SSRAtomRegistryLayer };
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// HTML Escaping
|
|
36
|
+
// =============================================================================
|
|
37
|
+
const escapeHtml = (str) => {
|
|
38
|
+
return str
|
|
39
|
+
.replace(/&/g, "&")
|
|
40
|
+
.replace(/</g, "<")
|
|
41
|
+
.replace(/>/g, ">")
|
|
42
|
+
.replace(/"/g, """)
|
|
43
|
+
.replace(/'/g, "'");
|
|
44
|
+
};
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Attribute Rendering
|
|
47
|
+
// =============================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Convert a prop name to its HTML attribute name
|
|
50
|
+
*/
|
|
51
|
+
const propToAttr = (prop) => {
|
|
52
|
+
// Handle className -> class
|
|
53
|
+
if (prop === "className")
|
|
54
|
+
return "class";
|
|
55
|
+
// Handle htmlFor -> for
|
|
56
|
+
if (prop === "htmlFor")
|
|
57
|
+
return "for";
|
|
58
|
+
// Convert camelCase to kebab-case for data-* and aria-*
|
|
59
|
+
if (prop.startsWith("data") || prop.startsWith("aria")) {
|
|
60
|
+
return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
return prop;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Render a single attribute to string
|
|
66
|
+
*/
|
|
67
|
+
const renderAttribute = (name, value) => {
|
|
68
|
+
if (value === true) {
|
|
69
|
+
return ` ${name}`;
|
|
70
|
+
}
|
|
71
|
+
if (value === false || value === null || value === undefined) {
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
return ` ${name}="${escapeHtml(String(value))}"`;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Render all props as HTML attributes
|
|
78
|
+
*/
|
|
79
|
+
const renderAttributes = (props) => {
|
|
80
|
+
let attrs = "";
|
|
81
|
+
for (const [key, value] of Object.entries(props)) {
|
|
82
|
+
if (isProperty(key)) {
|
|
83
|
+
attrs += renderAttribute(propToAttr(key), value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return attrs;
|
|
87
|
+
};
|
|
88
|
+
// =============================================================================
|
|
89
|
+
// Void Elements (self-closing)
|
|
90
|
+
// =============================================================================
|
|
91
|
+
const VOID_ELEMENTS = new Set([
|
|
92
|
+
"area", "base", "br", "col", "embed", "hr", "img", "input",
|
|
93
|
+
"link", "meta", "param", "source", "track", "wbr",
|
|
94
|
+
]);
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// Core Rendering
|
|
97
|
+
// =============================================================================
|
|
98
|
+
/**
|
|
99
|
+
* Check if a type is a function component
|
|
100
|
+
*/
|
|
101
|
+
const isFunctionComponent = (type) => typeof type === "function";
|
|
102
|
+
/**
|
|
103
|
+
* Check if a type is a host element (string tag)
|
|
104
|
+
*/
|
|
105
|
+
const isHostElement = (type) => typeof type === "string";
|
|
106
|
+
/**
|
|
107
|
+
* Render a VElement to HTML string.
|
|
108
|
+
* This is an Effect that requires AtomRegistry.
|
|
109
|
+
*/
|
|
110
|
+
const renderVElementToString = (vElement) => Effect.gen(function* () {
|
|
111
|
+
const type = vElement.type;
|
|
112
|
+
if (isFunctionComponent(type)) {
|
|
113
|
+
// Invoke the component, catching synchronous throws
|
|
114
|
+
const outputEffect = Effect.try({
|
|
115
|
+
try: () => type(vElement.props),
|
|
116
|
+
catch: (e) => e,
|
|
117
|
+
});
|
|
118
|
+
const output = yield* outputEffect;
|
|
119
|
+
// Normalize to get the VElement
|
|
120
|
+
let childVElement;
|
|
121
|
+
if (isStream(output)) {
|
|
122
|
+
// For streams, take first emission
|
|
123
|
+
const first = yield* Stream.runHead(output);
|
|
124
|
+
if (Option.isNone(first)) {
|
|
125
|
+
return ""; // Empty stream
|
|
126
|
+
}
|
|
127
|
+
childVElement = first.value;
|
|
128
|
+
}
|
|
129
|
+
else if (Effect.isEffect(output)) {
|
|
130
|
+
// Await the effect
|
|
131
|
+
childVElement = yield* output;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Plain VElement
|
|
135
|
+
childVElement = output;
|
|
136
|
+
}
|
|
137
|
+
// Recursively render the result
|
|
138
|
+
return yield* renderVElementToString(childVElement);
|
|
139
|
+
}
|
|
140
|
+
else if (type === "TEXT_ELEMENT") {
|
|
141
|
+
// Text node - escape and return
|
|
142
|
+
return escapeHtml(String(vElement.props.nodeValue ?? ""));
|
|
143
|
+
}
|
|
144
|
+
else if (type === "FRAGMENT") {
|
|
145
|
+
// Fragment - render children directly
|
|
146
|
+
const children = vElement.props.children ?? [];
|
|
147
|
+
let html = "";
|
|
148
|
+
for (const child of children) {
|
|
149
|
+
html += yield* renderVElementToString(child);
|
|
150
|
+
}
|
|
151
|
+
return html;
|
|
152
|
+
}
|
|
153
|
+
else if (type === "ERROR_BOUNDARY") {
|
|
154
|
+
// Error boundary - try to render children, catch errors and render fallback
|
|
155
|
+
const fallback = vElement.props.fallback;
|
|
156
|
+
const children = vElement.props.children;
|
|
157
|
+
const result = yield* Effect.either(Effect.gen(function* () {
|
|
158
|
+
let html = "";
|
|
159
|
+
for (const child of children) {
|
|
160
|
+
html += yield* renderVElementToString(child);
|
|
161
|
+
}
|
|
162
|
+
return html;
|
|
163
|
+
}));
|
|
164
|
+
if (result._tag === "Left") {
|
|
165
|
+
// Error occurred - render fallback
|
|
166
|
+
const onError = vElement.props.onError;
|
|
167
|
+
onError?.(result.left);
|
|
168
|
+
return yield* renderVElementToString(fallback);
|
|
169
|
+
}
|
|
170
|
+
return result.right;
|
|
171
|
+
}
|
|
172
|
+
else if (type === "SUSPENSE") {
|
|
173
|
+
// Suspense boundary - race child rendering against timeout
|
|
174
|
+
// Phase 5: If children complete first → resolved marker
|
|
175
|
+
// If timeout fires first → fallback marker
|
|
176
|
+
const fallback = vElement.props.fallback;
|
|
177
|
+
const threshold = vElement.props.threshold ?? 100;
|
|
178
|
+
const children = vElement.props.children;
|
|
179
|
+
// Create a Deferred to signal when children complete
|
|
180
|
+
const childrenComplete = yield* Deferred.make();
|
|
181
|
+
// Fork: render children to string
|
|
182
|
+
yield* Effect.fork(Effect.gen(function* () {
|
|
183
|
+
let childrenHtml = "";
|
|
184
|
+
for (const child of children) {
|
|
185
|
+
childrenHtml += yield* renderVElementToString(child);
|
|
186
|
+
}
|
|
187
|
+
yield* Deferred.succeed(childrenComplete, childrenHtml);
|
|
188
|
+
}).pipe(Effect.catchAll((e) => Deferred.fail(childrenComplete, e))));
|
|
189
|
+
// Race: children completing vs timeout
|
|
190
|
+
const result = yield* Effect.race(Deferred.await(childrenComplete).pipe(Effect.map((html) => ({ type: "resolved", html }))), Effect.sleep(`${threshold} millis`).pipe(Effect.as({ type: "timeout" })));
|
|
191
|
+
if (result.type === "resolved") {
|
|
192
|
+
// Children completed before timeout - render with resolved marker
|
|
193
|
+
return `<!--fibrae:sus:resolved-->${result.html}<!--/fibrae:sus-->`;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Timeout fired first - render fallback with fallback marker
|
|
197
|
+
const fallbackHtml = yield* renderVElementToString(fallback);
|
|
198
|
+
return `<!--fibrae:sus:fallback-->${fallbackHtml}<!--/fibrae:sus-->`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (isHostElement(type)) {
|
|
202
|
+
// Regular HTML element
|
|
203
|
+
const attrs = renderAttributes(vElement.props);
|
|
204
|
+
// Add data-key for keyed elements (needed for hydration)
|
|
205
|
+
const key = vElement.props.key;
|
|
206
|
+
const keyAttr = key != null ? ` data-key="${escapeHtml(String(key))}"` : "";
|
|
207
|
+
if (VOID_ELEMENTS.has(type)) {
|
|
208
|
+
return `<${type}${attrs}${keyAttr} />`;
|
|
209
|
+
}
|
|
210
|
+
// Render children with text node markers between adjacent text nodes
|
|
211
|
+
// This preserves text node boundaries for hydration (React's approach)
|
|
212
|
+
const children = vElement.props.children ?? [];
|
|
213
|
+
let childrenHtml = "";
|
|
214
|
+
let prevWasText = false;
|
|
215
|
+
for (const child of children) {
|
|
216
|
+
const isText = child.type === "TEXT_ELEMENT";
|
|
217
|
+
// Insert marker between adjacent text nodes to preserve boundaries
|
|
218
|
+
if (prevWasText && isText) {
|
|
219
|
+
childrenHtml += "<!--fibrae:$-->";
|
|
220
|
+
}
|
|
221
|
+
childrenHtml += yield* renderVElementToString(child);
|
|
222
|
+
prevWasText = isText;
|
|
223
|
+
}
|
|
224
|
+
return `<${type}${attrs}${keyAttr}>${childrenHtml}</${type}>`;
|
|
225
|
+
}
|
|
226
|
+
// Unknown type
|
|
227
|
+
return "";
|
|
228
|
+
});
|
|
229
|
+
// =============================================================================
|
|
230
|
+
// Public API
|
|
231
|
+
// =============================================================================
|
|
232
|
+
/**
|
|
233
|
+
* Render a VElement tree to an HTML string with serialized state.
|
|
234
|
+
*
|
|
235
|
+
* Returns the HTML and the dehydrated atom state array. You can serialize
|
|
236
|
+
* and embed the state in your HTML however you prefer (script tag, data
|
|
237
|
+
* attribute, separate endpoint, etc.).
|
|
238
|
+
*
|
|
239
|
+
* On the client, pass this state to render() via the initialState option
|
|
240
|
+
* to hydrate atom values before DOM hydration.
|
|
241
|
+
*
|
|
242
|
+
* Note: Atoms must use `Atom.serializable({ key, schema })` to be included
|
|
243
|
+
* in the dehydrated state.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* import { renderToString } from "fibrae/server";
|
|
248
|
+
*
|
|
249
|
+
* const program = Effect.gen(function* () {
|
|
250
|
+
* const { html, dehydratedState } = yield* renderToString(<App />);
|
|
251
|
+
*
|
|
252
|
+
* // Embed state however you prefer
|
|
253
|
+
* const page = `
|
|
254
|
+
* <!DOCTYPE html>
|
|
255
|
+
* <html>
|
|
256
|
+
* <body>
|
|
257
|
+
* <div id="root">${html}</div>
|
|
258
|
+
* <script>window.__STATE__ = ${JSON.stringify(dehydratedState)}</script>
|
|
259
|
+
* <script src="/client.js"></script>
|
|
260
|
+
* </body>
|
|
261
|
+
* </html>
|
|
262
|
+
* `;
|
|
263
|
+
* return page;
|
|
264
|
+
* });
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
export const renderToString = (element, _options) => Effect.gen(function* () {
|
|
268
|
+
const registry = yield* AtomRegistry.AtomRegistry;
|
|
269
|
+
// Render the element
|
|
270
|
+
const html = yield* renderVElementToString(element);
|
|
271
|
+
// Dehydrate the registry state
|
|
272
|
+
const dehydratedState = Hydration.dehydrate(registry);
|
|
273
|
+
return { html, dehydratedState };
|
|
274
|
+
}).pipe(Effect.provide(SSRAtomRegistryLayer));
|
|
275
|
+
/**
|
|
276
|
+
* Render a VElement tree to HTML, requiring AtomRegistry and any other
|
|
277
|
+
* services the component tree needs.
|
|
278
|
+
*
|
|
279
|
+
* Use this when your components require additional services (Navigator, RouterHandlers, etc.)
|
|
280
|
+
* that you want to provide yourself.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```typescript
|
|
284
|
+
* import { renderToStringWith, SSRAtomRegistryLayer } from "fibrae/server";
|
|
285
|
+
*
|
|
286
|
+
* const program = Effect.gen(function* () {
|
|
287
|
+
* const { html, dehydratedState } = yield* renderToStringWith(<App />);
|
|
288
|
+
* return { html, dehydratedState };
|
|
289
|
+
* });
|
|
290
|
+
*
|
|
291
|
+
* // Run with composed layers
|
|
292
|
+
* const result = Effect.runPromise(
|
|
293
|
+
* program.pipe(
|
|
294
|
+
* Effect.provide(Layer.mergeAll(
|
|
295
|
+
* SSRAtomRegistryLayer,
|
|
296
|
+
* navigatorLayer,
|
|
297
|
+
* routerHandlersLayer
|
|
298
|
+
* ))
|
|
299
|
+
* )
|
|
300
|
+
* );
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
303
|
+
export const renderToStringWith = (element, _options) => Effect.gen(function* () {
|
|
304
|
+
const registry = yield* AtomRegistry.AtomRegistry;
|
|
305
|
+
// Render the element
|
|
306
|
+
const html = yield* renderVElementToString(element);
|
|
307
|
+
// Dehydrate the registry state
|
|
308
|
+
const dehydratedState = Hydration.dehydrate(registry);
|
|
309
|
+
return { html, dehydratedState };
|
|
310
|
+
});
|
|
311
|
+
// Re-export Hydration for convenience
|
|
312
|
+
export { Hydration };
|
|
313
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as Option from "effect/Option";
|
|
2
|
+
import * as Stream from "effect/Stream";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Scope from "effect/Scope";
|
|
5
|
+
import * as Deferred from "effect/Deferred";
|
|
6
|
+
import * as Context from "effect/Context";
|
|
7
|
+
import { Atom as BaseAtom } from "@effect-atom/atom";
|
|
8
|
+
/**
|
|
9
|
+
* Primitive element types: HTML tags, text nodes, fragments, error boundary, or suspense
|
|
10
|
+
*/
|
|
11
|
+
export type Primitive = keyof HTMLElementTagNameMap | "TEXT_ELEMENT" | "FRAGMENT" | "ERROR_BOUNDARY" | "SUSPENSE";
|
|
12
|
+
/**
|
|
13
|
+
* Element type can be a primitive or a component function
|
|
14
|
+
* Components can return VElement, Effect, or Stream
|
|
15
|
+
*/
|
|
16
|
+
export type ElementType<Props = {}> = Primitive | ((props: Props) => VElement | Stream.Stream<VElement, any, any> | Effect.Effect<VElement, any, any>);
|
|
17
|
+
/**
|
|
18
|
+
* Virtual element representation - the core unit of the virtual DOM
|
|
19
|
+
*/
|
|
20
|
+
export interface VElement {
|
|
21
|
+
type: ElementType;
|
|
22
|
+
props: {
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
children?: VElement[];
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Mutable reference to a fiber for component instances
|
|
29
|
+
*/
|
|
30
|
+
export type FiberRef = {
|
|
31
|
+
current: Fiber;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Suspense boundary configuration
|
|
35
|
+
*
|
|
36
|
+
* Supports optimistic rendering: try children first, show fallback if they take
|
|
37
|
+
* too long. Suspended fibers are "parked" to continue processing in background.
|
|
38
|
+
*/
|
|
39
|
+
export type SuspenseConfig = {
|
|
40
|
+
fallback: VElement;
|
|
41
|
+
threshold: number;
|
|
42
|
+
showingFallback: boolean;
|
|
43
|
+
/** Reference to the original child fiber that's still processing in background */
|
|
44
|
+
parkedFiber: Option.Option<Fiber>;
|
|
45
|
+
/** Deferred that signals when parked fiber completes first render */
|
|
46
|
+
parkedComplete: Option.Option<Deferred.Deferred<void>>;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Error boundary configuration
|
|
50
|
+
*/
|
|
51
|
+
export type ErrorBoundaryConfig = {
|
|
52
|
+
fallback: VElement;
|
|
53
|
+
onError?: (cause: unknown) => void;
|
|
54
|
+
hasError: boolean;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Fiber node - represents a unit of work in the reconciliation tree
|
|
58
|
+
* Contains all state needed for rendering, effects, and diffing
|
|
59
|
+
*/
|
|
60
|
+
export interface Fiber {
|
|
61
|
+
type: Option.Option<ElementType>;
|
|
62
|
+
props: {
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
children?: VElement[];
|
|
65
|
+
};
|
|
66
|
+
dom: Option.Option<Node>;
|
|
67
|
+
parent: Option.Option<Fiber>;
|
|
68
|
+
child: Option.Option<Fiber>;
|
|
69
|
+
sibling: Option.Option<Fiber>;
|
|
70
|
+
alternate: Option.Option<Fiber>;
|
|
71
|
+
effectTag: Option.Option<"UPDATE" | "PLACEMENT" | "DELETION">;
|
|
72
|
+
componentScope: Option.Option<Scope.Scope>;
|
|
73
|
+
accessedAtoms: Option.Option<Set<BaseAtom.Atom<any>>>;
|
|
74
|
+
latestStreamValue: Option.Option<VElement>;
|
|
75
|
+
childFirstCommitDeferred: Option.Option<Deferred.Deferred<void>>;
|
|
76
|
+
fiberRef: Option.Option<FiberRef>;
|
|
77
|
+
isMultiEmissionStream: boolean;
|
|
78
|
+
errorBoundary: Option.Option<ErrorBoundaryConfig>;
|
|
79
|
+
suspense: Option.Option<SuspenseConfig>;
|
|
80
|
+
/** Context captured during render phase, used for event handlers in commit phase */
|
|
81
|
+
renderContext: Option.Option<Context.Context<unknown>>;
|
|
82
|
+
/** True if this fiber is parked (suspended) - scope should not be closed on deletion */
|
|
83
|
+
isParked: boolean;
|
|
84
|
+
/** True when fiber is being restored from parked state - skip component re-execution */
|
|
85
|
+
isUnparking: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Helper to check if a key is an event handler
|
|
89
|
+
*/
|
|
90
|
+
export declare const isEvent: (key: string) => boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Helper to check if a key is a regular property (not children, ref, key, or event)
|
|
93
|
+
*/
|
|
94
|
+
export declare const isProperty: (key: string) => boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Check if an element type is a primitive (string) or component (function)
|
|
97
|
+
*/
|
|
98
|
+
export declare const isPrimitive: (type: ElementType) => type is Primitive;
|
|
99
|
+
/**
|
|
100
|
+
* Check if element type is a component function
|
|
101
|
+
*/
|
|
102
|
+
export declare const isComponent: (type: ElementType) => type is (props: {}) => VElement | Stream.Stream<VElement, any, any> | Effect.Effect<VElement, any, any>;
|
|
103
|
+
export declare const isStream: (value: unknown) => value is Stream.Stream<any, any, any>;
|
|
104
|
+
declare const HydrationMismatch_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
|
|
105
|
+
readonly _tag: "HydrationMismatch";
|
|
106
|
+
} & Readonly<A>;
|
|
107
|
+
/**
|
|
108
|
+
* Error thrown when DOM structure doesn't match VElement tree during hydration.
|
|
109
|
+
*
|
|
110
|
+
* This is a tagged error that can be caught via Effect.catchTag("HydrationMismatch", ...).
|
|
111
|
+
*
|
|
112
|
+
* Structural mismatches (tag name, child count) indicate a bug where server and client
|
|
113
|
+
* rendered different component trees.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* yield* render(<App />, container, { initialState }).pipe(
|
|
118
|
+
* Effect.catchTag("HydrationMismatch", (err) => {
|
|
119
|
+
* console.error(`Hydration failed at ${err.path}: expected ${err.expected}, got ${err.actual}`);
|
|
120
|
+
* // Fallback: clear container and do fresh render
|
|
121
|
+
* container.innerHTML = "";
|
|
122
|
+
* return render(<App />, container);
|
|
123
|
+
* })
|
|
124
|
+
* );
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export declare class HydrationMismatch extends HydrationMismatch_base<{
|
|
128
|
+
/** What the VElement tree expected (e.g., "div", "3 children") */
|
|
129
|
+
readonly expected: string;
|
|
130
|
+
/** What the DOM actually had (e.g., "span", "2 children") */
|
|
131
|
+
readonly actual: string;
|
|
132
|
+
/** Human-readable path to the mismatch location (e.g., "div > ul > li:2") */
|
|
133
|
+
readonly path: string;
|
|
134
|
+
}> {
|
|
135
|
+
}
|
|
136
|
+
export {};
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as Option from "effect/Option";
|
|
2
|
+
import * as Stream from "effect/Stream";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Scope from "effect/Scope";
|
|
5
|
+
import * as Deferred from "effect/Deferred";
|
|
6
|
+
import * as Data from "effect/Data";
|
|
7
|
+
import * as Context from "effect/Context";
|
|
8
|
+
import { Atom as BaseAtom } from "@effect-atom/atom";
|
|
9
|
+
/**
|
|
10
|
+
* Helper to check if a key is an event handler
|
|
11
|
+
*/
|
|
12
|
+
export const isEvent = (key) => key.startsWith("on");
|
|
13
|
+
/**
|
|
14
|
+
* Helper to check if a key is a regular property (not children, ref, key, or event)
|
|
15
|
+
*/
|
|
16
|
+
export const isProperty = (key) => key !== "children" && key !== "ref" && key !== "key" && !isEvent(key);
|
|
17
|
+
/**
|
|
18
|
+
* Check if an element type is a primitive (string) or component (function)
|
|
19
|
+
*/
|
|
20
|
+
export const isPrimitive = (type) => typeof type === "string";
|
|
21
|
+
/**
|
|
22
|
+
* Check if element type is a component function
|
|
23
|
+
*/
|
|
24
|
+
export const isComponent = (type) => typeof type === "function";
|
|
25
|
+
export const isStream = (value) => (typeof value === "object" &&
|
|
26
|
+
value !== null &&
|
|
27
|
+
Stream.StreamTypeId in value);
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Hydration Errors
|
|
30
|
+
// =============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* Error thrown when DOM structure doesn't match VElement tree during hydration.
|
|
33
|
+
*
|
|
34
|
+
* This is a tagged error that can be caught via Effect.catchTag("HydrationMismatch", ...).
|
|
35
|
+
*
|
|
36
|
+
* Structural mismatches (tag name, child count) indicate a bug where server and client
|
|
37
|
+
* rendered different component trees.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* yield* render(<App />, container, { initialState }).pipe(
|
|
42
|
+
* Effect.catchTag("HydrationMismatch", (err) => {
|
|
43
|
+
* console.error(`Hydration failed at ${err.path}: expected ${err.expected}, got ${err.actual}`);
|
|
44
|
+
* // Fallback: clear container and do fresh render
|
|
45
|
+
* container.innerHTML = "";
|
|
46
|
+
* return render(<App />, container);
|
|
47
|
+
* })
|
|
48
|
+
* );
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export class HydrationMismatch extends Data.TaggedError("HydrationMismatch") {
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAwFrD;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAE7D;;GAEG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,EAAE,CACxC,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAExE;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAiB,EAAqB,EAAE,CAClE,OAAO,IAAI,KAAK,QAAQ,CAAC;AAE3B;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAiB,EAAE,EAAE,CAC/C,OAAO,IAAI,KAAK,UAAU,CAAC;AAE7B,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAyC,EAAE,CAAC,CACjF,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,KAAK,IAAI;IACd,MAAM,CAAC,YAAY,IAAI,KAAK,CAC7B,CAAC;AAEF,gFAAgF;AAChF,mBAAmB;AACnB,gFAAgF;AAEhF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,iBAAkB,SAAQ,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAOzE;CAAG"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Stream from "effect/Stream";
|
|
3
|
+
import * as Scope from "effect/Scope";
|
|
4
|
+
import { Atom, Registry as AtomRegistry } from "@effect-atom/atom";
|
|
5
|
+
import { type VElement } from "./shared.js";
|
|
6
|
+
import { FibraeRuntime } from "./runtime.js";
|
|
7
|
+
/**
|
|
8
|
+
* Normalize component output to a Stream.
|
|
9
|
+
* Components can return VElement, Effect<VElement, E>, or Stream<VElement, E>.
|
|
10
|
+
* Error type is preserved through the conversion.
|
|
11
|
+
*/
|
|
12
|
+
export declare const normalizeToStream: <E>(value: VElement | Effect.Effect<VElement, E, never> | Stream.Stream<VElement, E, never>) => Stream.Stream<VElement, E, never>;
|
|
13
|
+
/**
|
|
14
|
+
* Create a tracking registry that records which atoms are accessed
|
|
15
|
+
*/
|
|
16
|
+
export declare const makeTrackingRegistry: (realRegistry: AtomRegistry.Registry, accessedAtoms: Set<Atom.Atom<unknown>>) => AtomRegistry.Registry;
|
|
17
|
+
/**
|
|
18
|
+
* Subscribe to atom changes for reactivity.
|
|
19
|
+
* Uses registry.subscribe directly (like atom-react) instead of streams.
|
|
20
|
+
* This is simpler and more efficient - subscriptions are synchronous
|
|
21
|
+
* and cleanup is handled via scope finalizers.
|
|
22
|
+
*/
|
|
23
|
+
export declare const subscribeToAtoms: (atoms: Set<Atom.Atom<unknown>>, onUpdate: () => void, runtime: FibraeRuntime, scope: Scope.Scope.Closeable) => Effect.Effect<void, never, never>;
|