@cloudwerk/ui 0.4.0 → 0.6.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/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { HydrationManifest } from '@cloudwerk/core';
2
+
1
3
  /**
2
4
  * @cloudwerk/ui - Type Definitions
3
5
  *
@@ -131,6 +133,135 @@ interface PropsWithChildren<_P = unknown> {
131
133
  children?: unknown;
132
134
  }
133
135
 
136
+ /**
137
+ * @cloudwerk/ui - Hydration Utilities
138
+ *
139
+ * Server-side helpers for wrapping client components with hydration metadata
140
+ * and generating the client-side hydration bootstrap script.
141
+ */
142
+
143
+ /**
144
+ * Options for wrapping a component for hydration.
145
+ */
146
+ interface WrapForHydrationOptions {
147
+ /** Unique component ID */
148
+ componentId: string;
149
+ /** Props to serialize for client-side hydration */
150
+ props: Record<string, unknown>;
151
+ /** Custom wrapper element tag (default: 'div') */
152
+ wrapperTag?: string;
153
+ }
154
+ /**
155
+ * Options for generating hydration script.
156
+ */
157
+ interface HydrationScriptOptions {
158
+ /** Whether to include source maps in development */
159
+ includeSourceMaps?: boolean;
160
+ /** Custom hydration endpoint path */
161
+ hydrationEndpoint?: string;
162
+ }
163
+ /**
164
+ * Wrap a server-rendered element with hydration metadata.
165
+ *
166
+ * This wraps the component's HTML output with a container element
167
+ * that includes data attributes for client-side hydration:
168
+ * - `data-hydrate-id`: The component ID for looking up the client bundle
169
+ * - `data-hydrate-props`: JSON-serialized props to pass during hydration
170
+ *
171
+ * @param html - Server-rendered HTML string
172
+ * @param options - Hydration options
173
+ * @returns HTML string with hydration wrapper
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const html = wrapForHydration('<button>Count: 0</button>', {
178
+ * componentId: 'components_Counter',
179
+ * props: { initialCount: 0 },
180
+ * })
181
+ * // Returns:
182
+ * // <div data-hydrate-id="components_Counter" data-hydrate-props='{"initialCount":0}'>
183
+ * // <button>Count: 0</button>
184
+ * // </div>
185
+ * ```
186
+ */
187
+ declare function wrapForHydration(html: string, options: WrapForHydrationOptions): string;
188
+ /**
189
+ * Generate the hydration bootstrap script to include in the HTML response.
190
+ *
191
+ * This script:
192
+ * 1. Finds all elements with `data-hydrate-id` attributes
193
+ * 2. Loads the corresponding client bundles
194
+ * 3. Hydrates each component with its serialized props
195
+ *
196
+ * @param manifest - Hydration manifest with component metadata
197
+ * @param options - Script generation options
198
+ * @returns Script tags for hydration
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * const script = generateHydrationScript(manifest, {
203
+ * hydrationEndpoint: '/__cloudwerk',
204
+ * })
205
+ * // Insert at the end of the <body> tag
206
+ * ```
207
+ */
208
+ declare function generateHydrationScript(manifest: HydrationManifest, options?: HydrationScriptOptions): string;
209
+ /**
210
+ * Generate script tags for preloading client bundles.
211
+ *
212
+ * This adds modulepreload hints for better performance.
213
+ *
214
+ * @param manifest - Hydration manifest with component metadata
215
+ * @returns Link tags for modulepreload
216
+ */
217
+ declare function generatePreloadHints(manifest: HydrationManifest): string;
218
+ /**
219
+ * Generate the hydration runtime module for Hono JSX.
220
+ *
221
+ * This is served at `/__cloudwerk/runtime.js` and provides the render
222
+ * function that uses hono/jsx/dom for client-side hydration.
223
+ *
224
+ * The render function uses hono/jsx/dom's built-in virtual DOM diffing
225
+ * to safely update the DOM without raw HTML insertion.
226
+ *
227
+ * @returns JavaScript module source code
228
+ */
229
+ declare function generateHydrationRuntime(): string;
230
+ /**
231
+ * Generate the React hydration runtime module.
232
+ *
233
+ * This is served at `/__cloudwerk/react-runtime.js` and provides the
234
+ * hydrateRoot function from react-dom/client for client-side hydration.
235
+ *
236
+ * The runtime exports:
237
+ * - hydrateRoot from react-dom/client for hydration
238
+ * - React and all React hooks for client components
239
+ *
240
+ * @returns JavaScript module source code
241
+ */
242
+ declare function generateReactHydrationRuntime(): string;
243
+ /**
244
+ * Generate the React hydration bootstrap script to include in the HTML response.
245
+ *
246
+ * This script:
247
+ * 1. Finds all elements with `data-hydrate-id` attributes
248
+ * 2. Loads the corresponding client bundles
249
+ * 3. Hydrates each component with React's hydrateRoot
250
+ *
251
+ * @param manifest - Hydration manifest with component metadata
252
+ * @param options - Script generation options
253
+ * @returns Script tags for hydration
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * const script = generateReactHydrationScript(manifest, {
258
+ * hydrationEndpoint: '/__cloudwerk',
259
+ * })
260
+ * // Insert at the end of the <body> tag
261
+ * ```
262
+ */
263
+ declare function generateReactHydrationScript(manifest: HydrationManifest, options?: HydrationScriptOptions): string;
264
+
134
265
  /**
135
266
  * @cloudwerk/ui - Renderer Selection
136
267
  *
@@ -156,12 +287,32 @@ declare function getActiveRenderer(): Renderer;
156
287
  * console.log(`Using ${getActiveRendererName()} renderer`)
157
288
  */
158
289
  declare function getActiveRendererName(): string;
290
+ /**
291
+ * Initialize and register the React renderer.
292
+ *
293
+ * This must be called before using setActiveRenderer('react').
294
+ * Requires react and react-dom packages to be installed.
295
+ *
296
+ * @throws Error if React packages are not installed
297
+ *
298
+ * @example
299
+ * import { initReactRenderer, setActiveRenderer } from '@cloudwerk/ui'
300
+ *
301
+ * // Initialize React renderer (requires react and react-dom)
302
+ * await initReactRenderer()
303
+ *
304
+ * // Now you can use React
305
+ * setActiveRenderer('react')
306
+ */
307
+ declare function initReactRenderer(): Promise<void>;
159
308
  /**
160
309
  * Set the active renderer by name.
161
310
  *
162
311
  * Called during app initialization based on the `ui.renderer` config option.
163
312
  * The renderer must be registered (either built-in or via registerRenderer).
164
313
  *
314
+ * For the React renderer, you must call initReactRenderer() first.
315
+ *
165
316
  * @param name - Renderer name from config (e.g., 'hono-jsx', 'react')
166
317
  * @throws Error if renderer is not found
167
318
  *
@@ -197,7 +348,7 @@ declare function registerRenderer(name: string, renderer: Renderer): void;
197
348
  *
198
349
  * @example
199
350
  * const available = getAvailableRenderers()
200
- * // ['hono-jsx']
351
+ * // ['hono-jsx'] (or ['hono-jsx', 'react'] if initReactRenderer() was called)
201
352
  */
202
353
  declare function getAvailableRenderers(): string[];
203
354
  /**
@@ -379,4 +530,4 @@ declare function html(content: string, options?: HtmlOptions): Response;
379
530
  */
380
531
  declare function hydrate(element: unknown, root: Element): void;
381
532
 
382
- export { type HtmlOptions, type PropsWithChildren, type RenderOptions, type RenderToStreamOptions, type Renderer, type StreamRenderOptions, _resetRenderers, getActiveRenderer, getActiveRendererName, getAvailableRenderers, honoJsxRenderer, html, hydrate, registerRenderer, render, renderStream, renderToStream, setActiveRenderer };
533
+ export { type HtmlOptions, type HydrationScriptOptions, type PropsWithChildren, type RenderOptions, type RenderToStreamOptions, type Renderer, type StreamRenderOptions, type WrapForHydrationOptions, _resetRenderers, generateHydrationRuntime, generateHydrationScript, generatePreloadHints, generateReactHydrationRuntime, generateReactHydrationScript, getActiveRenderer, getActiveRendererName, getAvailableRenderers, honoJsxRenderer, html, hydrate, initReactRenderer, registerRenderer, render, renderStream, renderToStream, setActiveRenderer, wrapForHydration };
package/dist/index.js CHANGED
@@ -46,17 +46,28 @@ var honoJsxRenderer = {
46
46
  /**
47
47
  * Hydrate a JSX element on the client.
48
48
  *
49
- * This is a placeholder that throws an informative error.
50
- * Client-side hydration will be implemented in issue #39.
49
+ * Uses hono/jsx/dom render function to attach event handlers and state
50
+ * to server-rendered HTML. This is called by the client-side hydration
51
+ * bootstrap script for each Client Component.
51
52
  *
52
- * @param _element - JSX element (unused)
53
- * @param _root - DOM element (unused)
54
- * @throws Error with information about when this feature will be available
53
+ * Note: This method is primarily used by the client-side hydration runtime.
54
+ * In server-side code, it will throw an error since the DOM is not available.
55
+ *
56
+ * @param element - JSX element to hydrate
57
+ * @param root - DOM element to hydrate into
58
+ * @throws Error if called in a non-browser environment
55
59
  */
56
- hydrate(_element, _root) {
57
- throw new Error(
58
- "Client hydration requires hono/jsx/dom. This feature will be available after issue #39 is implemented."
59
- );
60
+ hydrate(element, root) {
61
+ if (typeof window === "undefined" || typeof document === "undefined") {
62
+ throw new Error(
63
+ "hydrate() can only be called in a browser environment. For server-side rendering, use render() instead."
64
+ );
65
+ }
66
+ import("hono/jsx/dom").then(({ render: render2 }) => {
67
+ render2(element, root);
68
+ }).catch((error) => {
69
+ console.error("[Cloudwerk] Failed to hydrate component:", error);
70
+ });
60
71
  }
61
72
  };
62
73
  function renderStream(loadingElement, contentPromise, options = {}) {
@@ -144,10 +155,29 @@ function getActiveRenderer() {
144
155
  function getActiveRendererName() {
145
156
  return activeRendererName;
146
157
  }
158
+ async function initReactRenderer() {
159
+ if (renderers["react"]) {
160
+ return;
161
+ }
162
+ try {
163
+ const { reactRenderer } = await import("./react-PVIKZSJC.js");
164
+ renderers["react"] = reactRenderer;
165
+ } catch (error) {
166
+ throw new Error(
167
+ `Failed to initialize React renderer. Make sure react and react-dom are installed: npm install react react-dom
168
+ Original error: ${error instanceof Error ? error.message : String(error)}`
169
+ );
170
+ }
171
+ }
147
172
  function setActiveRenderer(name) {
148
173
  const renderer = renderers[name];
149
174
  if (!renderer) {
150
175
  const available = Object.keys(renderers).join(", ");
176
+ if (name === "react") {
177
+ throw new Error(
178
+ `React renderer is not initialized. Call initReactRenderer() first, or install react and react-dom packages.`
179
+ );
180
+ }
151
181
  throw new Error(`Unknown renderer "${name}". Available renderers: ${available}`);
152
182
  }
153
183
  activeRenderer = renderer;
@@ -174,6 +204,266 @@ function _resetRenderers() {
174
204
  activeRendererName = "hono-jsx";
175
205
  }
176
206
 
207
+ // src/hydration.ts
208
+ import { serializeProps } from "@cloudwerk/core";
209
+ function wrapForHydration(html2, options) {
210
+ const { componentId, props, wrapperTag = "div" } = options;
211
+ const serializedProps = serializeProps(props);
212
+ const escapedProps = escapeHtmlAttribute(serializedProps);
213
+ return `<${wrapperTag} data-hydrate-id="${componentId}" data-hydrate-props="${escapedProps}">${html2}</${wrapperTag}>`;
214
+ }
215
+ function escapeHtmlAttribute(str) {
216
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
217
+ }
218
+ function generateHydrationScript(manifest, options = {}) {
219
+ const { hydrationEndpoint = "/__cloudwerk" } = options;
220
+ if (manifest.components.size === 0) {
221
+ return "";
222
+ }
223
+ const bundleMap = {};
224
+ for (const [id, meta] of manifest.components) {
225
+ bundleMap[id] = meta.bundlePath;
226
+ }
227
+ const script = `
228
+ <script type="module">
229
+ (async function() {
230
+ // Bundle map for component lookups
231
+ const bundles = ${JSON.stringify(bundleMap)};
232
+
233
+ // Find all elements that need hydration
234
+ const elements = document.querySelectorAll('[data-hydrate-id]');
235
+ if (elements.length === 0) return;
236
+
237
+ // Cache for loaded modules
238
+ const moduleCache = new Map();
239
+
240
+ // Load a component module
241
+ async function loadComponent(bundlePath) {
242
+ if (moduleCache.has(bundlePath)) {
243
+ return moduleCache.get(bundlePath);
244
+ }
245
+ const module = await import(bundlePath);
246
+ moduleCache.set(bundlePath, module);
247
+ return module;
248
+ }
249
+
250
+ // Hydrate each element
251
+ for (const el of elements) {
252
+ const componentId = el.getAttribute('data-hydrate-id');
253
+ const propsJson = el.getAttribute('data-hydrate-props');
254
+
255
+ if (!componentId || !bundles[componentId]) {
256
+ console.warn('[Cloudwerk] Unknown client component:', componentId);
257
+ continue;
258
+ }
259
+
260
+ try {
261
+ // Parse props
262
+ const props = propsJson ? JSON.parse(propsJson) : {};
263
+
264
+ // Load the component module
265
+ const bundlePath = bundles[componentId];
266
+ const module = await loadComponent(bundlePath);
267
+ const Component = module.default;
268
+
269
+ if (!Component) {
270
+ console.error('[Cloudwerk] No default export in component:', componentId);
271
+ continue;
272
+ }
273
+
274
+ // Import hono/jsx/dom for hydration
275
+ const { render } = await import('${hydrationEndpoint}/runtime.js');
276
+
277
+ // Hydrate the component using hono/jsx/dom render
278
+ // This safely replaces content using virtual DOM diffing
279
+ render(Component(props), el);
280
+
281
+ // Remove hydration attributes after successful hydration
282
+ el.removeAttribute('data-hydrate-id');
283
+ el.removeAttribute('data-hydrate-props');
284
+ } catch (error) {
285
+ console.error('[Cloudwerk] Failed to hydrate component:', componentId, error);
286
+ }
287
+ }
288
+ })();
289
+ </script>
290
+ `.trim();
291
+ return script;
292
+ }
293
+ function generatePreloadHints(manifest) {
294
+ if (manifest.components.size === 0) {
295
+ return "";
296
+ }
297
+ const hints = [];
298
+ for (const meta of manifest.components.values()) {
299
+ hints.push(`<link rel="modulepreload" href="${meta.bundlePath}">`);
300
+ }
301
+ return hints.join("\n");
302
+ }
303
+ function generateHydrationRuntime() {
304
+ return `
305
+ // Cloudwerk Hydration Runtime
306
+ // Uses hono/jsx/dom for client-side rendering with virtual DOM diffing
307
+ import { render as honoRender } from 'hono/jsx/dom';
308
+
309
+ export function render(element, container) {
310
+ // Use hono/jsx/dom render which safely updates DOM via virtual DOM diffing
311
+ // This replaces the server-rendered content with the interactive version
312
+ honoRender(element, container);
313
+ }
314
+
315
+ // Re-export hooks for client components
316
+ export {
317
+ useState,
318
+ useEffect,
319
+ useRef,
320
+ useCallback,
321
+ useMemo,
322
+ useReducer,
323
+ useSyncExternalStore,
324
+ useTransition,
325
+ useDeferredValue,
326
+ useId,
327
+ } from 'hono/jsx/dom';
328
+ `.trim();
329
+ }
330
+ function generateReactHydrationRuntime() {
331
+ return `
332
+ // Cloudwerk React Hydration Runtime
333
+ // Uses react-dom/client for client-side hydration
334
+ import React from 'react';
335
+ import { hydrateRoot } from 'react-dom/client';
336
+ import {
337
+ useState,
338
+ useEffect,
339
+ useRef,
340
+ useCallback,
341
+ useMemo,
342
+ useReducer,
343
+ useContext,
344
+ useLayoutEffect,
345
+ useImperativeHandle,
346
+ useDebugValue,
347
+ useSyncExternalStore,
348
+ useTransition,
349
+ useDeferredValue,
350
+ useId,
351
+ useInsertionEffect,
352
+ useOptimistic,
353
+ useActionState,
354
+ use,
355
+ } from 'react';
356
+
357
+ // Re-export React for component rendering
358
+ export { React };
359
+
360
+ // Re-export hydrateRoot for hydration
361
+ export { hydrateRoot };
362
+
363
+ // Hydrate function that wraps hydrateRoot for Cloudwerk usage
364
+ export function hydrate(Component, props, container) {
365
+ return hydrateRoot(container, React.createElement(Component, props));
366
+ }
367
+
368
+ // Re-export all hooks for client components
369
+ export {
370
+ useState,
371
+ useEffect,
372
+ useRef,
373
+ useCallback,
374
+ useMemo,
375
+ useReducer,
376
+ useContext,
377
+ useLayoutEffect,
378
+ useImperativeHandle,
379
+ useDebugValue,
380
+ useSyncExternalStore,
381
+ useTransition,
382
+ useDeferredValue,
383
+ useId,
384
+ useInsertionEffect,
385
+ useOptimistic,
386
+ useActionState,
387
+ use,
388
+ };
389
+ `.trim();
390
+ }
391
+ function generateReactHydrationScript(manifest, options = {}) {
392
+ const { hydrationEndpoint = "/__cloudwerk" } = options;
393
+ if (manifest.components.size === 0) {
394
+ return "";
395
+ }
396
+ const bundleMap = {};
397
+ for (const [id, meta] of manifest.components) {
398
+ bundleMap[id] = meta.bundlePath;
399
+ }
400
+ const script = `
401
+ <script type="module">
402
+ (async function() {
403
+ // Bundle map for component lookups
404
+ const bundles = ${JSON.stringify(bundleMap)};
405
+
406
+ // Find all elements that need hydration
407
+ const elements = document.querySelectorAll('[data-hydrate-id]');
408
+ if (elements.length === 0) return;
409
+
410
+ // Cache for loaded modules
411
+ const moduleCache = new Map();
412
+
413
+ // Load a component module
414
+ async function loadComponent(bundlePath) {
415
+ if (moduleCache.has(bundlePath)) {
416
+ return moduleCache.get(bundlePath);
417
+ }
418
+ const module = await import(bundlePath);
419
+ moduleCache.set(bundlePath, module);
420
+ return module;
421
+ }
422
+
423
+ // Import React and hydrateRoot from the runtime
424
+ const { React, hydrateRoot } = await import('${hydrationEndpoint}/react-runtime.js');
425
+
426
+ // Hydrate each element
427
+ for (const el of elements) {
428
+ const componentId = el.getAttribute('data-hydrate-id');
429
+ const propsJson = el.getAttribute('data-hydrate-props');
430
+
431
+ if (!componentId || !bundles[componentId]) {
432
+ console.warn('[Cloudwerk] Unknown client component:', componentId);
433
+ continue;
434
+ }
435
+
436
+ try {
437
+ // Parse props
438
+ const props = propsJson ? JSON.parse(propsJson) : {};
439
+
440
+ // Load the component module
441
+ const bundlePath = bundles[componentId];
442
+ const module = await loadComponent(bundlePath);
443
+ const Component = module.default;
444
+
445
+ if (!Component) {
446
+ console.error('[Cloudwerk] No default export in component:', componentId);
447
+ continue;
448
+ }
449
+
450
+ // Hydrate the component using React's hydrateRoot
451
+ // This attaches event handlers to the server-rendered HTML
452
+ hydrateRoot(el, React.createElement(Component, props));
453
+
454
+ // Remove hydration attributes after successful hydration
455
+ el.removeAttribute('data-hydrate-id');
456
+ el.removeAttribute('data-hydrate-props');
457
+ } catch (error) {
458
+ console.error('[Cloudwerk] Failed to hydrate component:', componentId, error);
459
+ }
460
+ }
461
+ })();
462
+ </script>
463
+ `.trim();
464
+ return script;
465
+ }
466
+
177
467
  // src/index.ts
178
468
  function render(element, options) {
179
469
  return getActiveRenderer().render(element, options);
@@ -186,15 +476,22 @@ function hydrate(element, root) {
186
476
  }
187
477
  export {
188
478
  _resetRenderers,
479
+ generateHydrationRuntime,
480
+ generateHydrationScript,
481
+ generatePreloadHints,
482
+ generateReactHydrationRuntime,
483
+ generateReactHydrationScript,
189
484
  getActiveRenderer,
190
485
  getActiveRendererName,
191
486
  getAvailableRenderers,
192
487
  honoJsxRenderer,
193
488
  html,
194
489
  hydrate,
490
+ initReactRenderer,
195
491
  registerRenderer,
196
492
  render,
197
493
  renderStream,
198
494
  renderToStream,
199
- setActiveRenderer
495
+ setActiveRenderer,
496
+ wrapForHydration
200
497
  };
@@ -0,0 +1,104 @@
1
+ // src/renderers/react.ts
2
+ import { renderToString, renderToReadableStream } from "react-dom/server";
3
+ var reactRenderer = {
4
+ /**
5
+ * Render a React element to an HTML Response.
6
+ *
7
+ * Uses React's renderToString() for synchronous server-side rendering.
8
+ * This method wraps the output in a proper Response object with headers.
9
+ *
10
+ * @param element - React element (React.ReactElement)
11
+ * @param options - Render options
12
+ * @returns Response object with HTML content
13
+ */
14
+ render(element, options = {}) {
15
+ const { status = 200, headers = {}, doctype = true } = options;
16
+ const htmlString = renderToString(element);
17
+ const body = doctype ? `<!DOCTYPE html>${htmlString}` : htmlString;
18
+ return new Response(body, {
19
+ status,
20
+ headers: {
21
+ "Content-Type": "text/html; charset=utf-8",
22
+ ...headers
23
+ }
24
+ });
25
+ },
26
+ /**
27
+ * Create an HTML Response from a raw string.
28
+ *
29
+ * Useful for static HTML, templates, or pre-rendered content
30
+ * that doesn't need to go through JSX rendering.
31
+ *
32
+ * @param content - Raw HTML string
33
+ * @param options - HTML response options
34
+ * @returns Response object with HTML content
35
+ */
36
+ html(content, options = {}) {
37
+ const { status = 200, headers = {} } = options;
38
+ return new Response(content, {
39
+ status,
40
+ headers: {
41
+ "Content-Type": "text/html; charset=utf-8",
42
+ ...headers
43
+ }
44
+ });
45
+ },
46
+ /**
47
+ * Hydrate a React element on the client.
48
+ *
49
+ * Uses react-dom/client's hydrateRoot to attach event handlers and state
50
+ * to server-rendered HTML. This is called by the client-side hydration
51
+ * runtime for each Client Component.
52
+ *
53
+ * Note: This method is primarily used by the client-side hydration runtime.
54
+ * In server-side code, it will throw an error since the DOM is not available.
55
+ *
56
+ * @param element - React element to hydrate
57
+ * @param root - DOM element to hydrate into
58
+ * @throws Error if called in a non-browser environment
59
+ */
60
+ hydrate(element, root) {
61
+ if (typeof window === "undefined" || typeof document === "undefined") {
62
+ throw new Error(
63
+ "hydrate() can only be called in a browser environment. For server-side rendering, use render() instead."
64
+ );
65
+ }
66
+ import("react-dom/client").then(({ hydrateRoot }) => {
67
+ hydrateRoot(root, element);
68
+ }).catch((error) => {
69
+ console.error("[Cloudwerk] Failed to hydrate React component:", error);
70
+ });
71
+ }
72
+ };
73
+ function prependDoctype(stream) {
74
+ const encoder = new TextEncoder();
75
+ const doctypeBytes = encoder.encode("<!DOCTYPE html>");
76
+ let doctypeSent = false;
77
+ return stream.pipeThrough(
78
+ new TransformStream({
79
+ transform(chunk, controller) {
80
+ if (!doctypeSent) {
81
+ controller.enqueue(doctypeBytes);
82
+ doctypeSent = true;
83
+ }
84
+ controller.enqueue(chunk);
85
+ }
86
+ })
87
+ );
88
+ }
89
+ async function reactRenderToStream(element, options = {}) {
90
+ const { status = 200, headers = {}, doctype = true } = options;
91
+ const contentStream = await renderToReadableStream(element);
92
+ const stream = doctype ? prependDoctype(contentStream) : contentStream;
93
+ return new Response(stream, {
94
+ status,
95
+ headers: {
96
+ "Content-Type": "text/html; charset=utf-8",
97
+ ...headers
98
+ }
99
+ });
100
+ }
101
+ export {
102
+ reactRenderToStream,
103
+ reactRenderer
104
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudwerk/ui",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "UI rendering abstraction for Cloudwerk",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,20 +17,36 @@
17
17
  "files": [
18
18
  "dist"
19
19
  ],
20
- "dependencies": {},
20
+ "dependencies": {
21
+ "@cloudwerk/core": "0.6.0"
22
+ },
21
23
  "peerDependencies": {
22
- "hono": "^4.0.0"
24
+ "hono": "^4.0.0",
25
+ "react": "^19.0.0",
26
+ "react-dom": "^19.0.0"
27
+ },
28
+ "peerDependenciesMeta": {
29
+ "react": {
30
+ "optional": true
31
+ },
32
+ "react-dom": {
33
+ "optional": true
34
+ }
23
35
  },
24
36
  "devDependencies": {
37
+ "@types/react": "^19.0.0",
38
+ "@types/react-dom": "^19.0.0",
25
39
  "@vitest/coverage-v8": "^1.0.0",
26
40
  "hono": "^4.7.4",
41
+ "react": "^19.0.0",
42
+ "react-dom": "^19.0.0",
27
43
  "tsup": "^8.0.0",
28
44
  "typescript": "^5.0.0",
29
45
  "vitest": "^1.0.0"
30
46
  },
31
47
  "scripts": {
32
- "build": "tsup src/index.ts --format esm --dts",
33
- "dev": "tsup src/index.ts --format esm --dts --watch",
48
+ "build": "tsup",
49
+ "dev": "tsup --watch",
34
50
  "test": "vitest --run",
35
51
  "test:watch": "vitest",
36
52
  "test:coverage": "vitest --run --coverage",