@canonical/react-ssr 0.21.0 → 0.23.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.
Files changed (83) hide show
  1. package/dist/esm/bin/serve-express.js +86 -0
  2. package/dist/esm/bin/serve-express.js.map +1 -0
  3. package/dist/esm/index.js +2 -2
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/esm/lib/index.js +3 -0
  6. package/dist/esm/lib/index.js.map +1 -0
  7. package/dist/esm/lib/renderer/Extractor.js +172 -0
  8. package/dist/esm/lib/renderer/Extractor.js.map +1 -0
  9. package/dist/esm/lib/renderer/JSXRenderer.js +283 -0
  10. package/dist/esm/lib/renderer/JSXRenderer.js.map +1 -0
  11. package/dist/esm/lib/renderer/SitemapRenderer.js +241 -0
  12. package/dist/esm/lib/renderer/SitemapRenderer.js.map +1 -0
  13. package/dist/esm/lib/renderer/TextRenderer.js +124 -0
  14. package/dist/esm/lib/renderer/TextRenderer.js.map +1 -0
  15. package/dist/esm/lib/renderer/constants.js.map +1 -0
  16. package/dist/esm/lib/renderer/index.js +6 -0
  17. package/dist/esm/lib/renderer/index.js.map +1 -0
  18. package/dist/esm/lib/renderer/types.js +10 -0
  19. package/dist/esm/lib/renderer/types.js.map +1 -0
  20. package/dist/esm/lib/server/index.js +3 -0
  21. package/dist/esm/lib/server/index.js.map +1 -0
  22. package/dist/esm/lib/server/serveStream.js +53 -0
  23. package/dist/esm/lib/server/serveStream.js.map +1 -0
  24. package/dist/esm/lib/server/serveString.js +49 -0
  25. package/dist/esm/lib/server/serveString.js.map +1 -0
  26. package/dist/types/bin/serve-express.d.ts +24 -0
  27. package/dist/types/bin/serve-express.d.ts.map +1 -0
  28. package/dist/types/index.d.ts +2 -2
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/dist/types/lib/index.d.ts +3 -0
  31. package/dist/types/lib/index.d.ts.map +1 -0
  32. package/dist/types/lib/renderer/Extractor.d.ts +93 -0
  33. package/dist/types/lib/renderer/Extractor.d.ts.map +1 -0
  34. package/dist/types/lib/renderer/JSXRenderer.d.ts +163 -0
  35. package/dist/types/lib/renderer/JSXRenderer.d.ts.map +1 -0
  36. package/dist/types/lib/renderer/SitemapRenderer.d.ts +153 -0
  37. package/dist/types/lib/renderer/SitemapRenderer.d.ts.map +1 -0
  38. package/dist/types/lib/renderer/TextRenderer.d.ts +83 -0
  39. package/dist/types/lib/renderer/TextRenderer.d.ts.map +1 -0
  40. package/dist/types/lib/renderer/constants.d.ts.map +1 -0
  41. package/dist/types/lib/renderer/index.d.ts +7 -0
  42. package/dist/types/lib/renderer/index.d.ts.map +1 -0
  43. package/dist/types/lib/renderer/types.d.ts +161 -0
  44. package/dist/types/lib/renderer/types.d.ts.map +1 -0
  45. package/dist/types/lib/server/index.d.ts +3 -0
  46. package/dist/types/lib/server/index.d.ts.map +1 -0
  47. package/dist/types/lib/server/serveStream.d.ts +41 -0
  48. package/dist/types/lib/server/serveStream.d.ts.map +1 -0
  49. package/dist/types/lib/server/serveString.d.ts +37 -0
  50. package/dist/types/lib/server/serveString.d.ts.map +1 -0
  51. package/package.json +32 -17
  52. package/dist/esm/renderer/Extractor.js +0 -127
  53. package/dist/esm/renderer/Extractor.js.map +0 -1
  54. package/dist/esm/renderer/JSXRenderer.js +0 -168
  55. package/dist/esm/renderer/JSXRenderer.js.map +0 -1
  56. package/dist/esm/renderer/constants.js.map +0 -1
  57. package/dist/esm/renderer/index.js +0 -4
  58. package/dist/esm/renderer/index.js.map +0 -1
  59. package/dist/esm/renderer/types.js +0 -2
  60. package/dist/esm/renderer/types.js.map +0 -1
  61. package/dist/esm/server/index.js +0 -2
  62. package/dist/esm/server/index.js.map +0 -1
  63. package/dist/esm/server/serve-express.js +0 -58
  64. package/dist/esm/server/serve-express.js.map +0 -1
  65. package/dist/esm/server/serve.js +0 -41
  66. package/dist/esm/server/serve.js.map +0 -1
  67. package/dist/types/renderer/Extractor.d.ts +0 -68
  68. package/dist/types/renderer/Extractor.d.ts.map +0 -1
  69. package/dist/types/renderer/JSXRenderer.d.ts +0 -71
  70. package/dist/types/renderer/JSXRenderer.d.ts.map +0 -1
  71. package/dist/types/renderer/constants.d.ts.map +0 -1
  72. package/dist/types/renderer/index.d.ts +0 -5
  73. package/dist/types/renderer/index.d.ts.map +0 -1
  74. package/dist/types/renderer/types.d.ts +0 -35
  75. package/dist/types/renderer/types.d.ts.map +0 -1
  76. package/dist/types/server/index.d.ts +0 -2
  77. package/dist/types/server/index.d.ts.map +0 -1
  78. package/dist/types/server/serve-express.d.ts +0 -3
  79. package/dist/types/server/serve-express.d.ts.map +0 -1
  80. package/dist/types/server/serve.d.ts +0 -30
  81. package/dist/types/server/serve.d.ts.map +0 -1
  82. /package/dist/esm/{renderer → lib/renderer}/constants.js +0 -0
  83. /package/dist/types/{renderer → lib/renderer}/constants.d.ts +0 -0
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone Express server for serving an SSR application.
4
+ *
5
+ * Dynamically imports a renderer module that exports a factory function.
6
+ * The factory receives a Node `IncomingMessage` and returns a renderer.
7
+ * Supports both streaming and string rendering modes.
8
+ *
9
+ * @example
10
+ * ```sh
11
+ * # String rendering (default), one static directory
12
+ * serve-express dist/server/renderer.js --static assets:dist/client/assets
13
+ *
14
+ * # Streaming, multiple static directories
15
+ * serve-express dist/server/renderer.js --streaming \
16
+ * --static assets:dist/client/assets \
17
+ * --static public:dist/client/public
18
+ *
19
+ * # Custom port
20
+ * serve-express dist/server/renderer.js -p 3000 --static assets:dist/client/assets
21
+ * ```
22
+ */
23
+ import path from "node:path";
24
+ import { parseArgs } from "node:util";
25
+ import express from "express";
26
+ import { serveStream } from "#server";
27
+ /**
28
+ * Parse a `route:filepath` string into its constituent parts.
29
+ *
30
+ * @param pair - A string in the format `"route:filepath"`, e.g. `"assets:dist/client/assets"`.
31
+ * @returns An object with the route prefix and the absolute filesystem path.
32
+ */
33
+ function parseStaticPair(pair) {
34
+ const separatorIndex = pair.indexOf(":");
35
+ if (separatorIndex === -1) {
36
+ return { route: `/${pair}`, dir: path.join(process.cwd(), pair) };
37
+ }
38
+ const route = pair.slice(0, separatorIndex);
39
+ const filepath = pair.slice(separatorIndex + 1);
40
+ return {
41
+ route: `/${route}`,
42
+ dir: path.join(process.cwd(), filepath),
43
+ };
44
+ }
45
+ const { values, positionals } = parseArgs({
46
+ args: process.argv.slice(2),
47
+ options: {
48
+ port: {
49
+ type: "string",
50
+ alias: "p",
51
+ default: "5173",
52
+ },
53
+ static: {
54
+ type: "string",
55
+ alias: "s",
56
+ multiple: true,
57
+ default: ["assets:dist/client/assets"],
58
+ },
59
+ streaming: {
60
+ type: "boolean",
61
+ default: false,
62
+ },
63
+ },
64
+ strict: true,
65
+ allowPositionals: true,
66
+ });
67
+ const port = Number(values.port) || 5173;
68
+ const rendererFilePath = path.join(process.cwd(), positionals[0]);
69
+ if (!rendererFilePath) {
70
+ console.error("Usage: serve-express <renderer-path> [--static route:path ...] [--streaming] [-p port]");
71
+ process.exit(1);
72
+ }
73
+ const createRenderer = await import(rendererFilePath).then((m) => m.default);
74
+ if (typeof createRenderer !== "function") {
75
+ throw new Error("Renderer module must default-export a factory function (req) => Renderer.");
76
+ }
77
+ const app = express();
78
+ for (const pair of values.static ?? []) {
79
+ const { route, dir } = parseStaticPair(pair);
80
+ app.use(route, express.static(dir));
81
+ }
82
+ app.use(serveStream(createRenderer));
83
+ app.listen(port, () => {
84
+ console.log(`Server started on http://localhost:${port}/`);
85
+ });
86
+ //# sourceMappingURL=serve-express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve-express.js","sourceRoot":"","sources":["../../../src/bin/serve-express.ts"],"names":[],"mappings":"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"}
package/dist/esm/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export * as renderer from "./renderer/index.js";
2
- export * as server from "./server/index.js";
1
+ export * as renderer from "./lib/renderer/index.js";
2
+ export * as server from "./lib/server/index.js";
3
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * as renderer from "./renderer/index.js";
2
+ export * as server from "./server/index.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,MAAM,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,172 @@
1
+ import { toCamelCase } from "@canonical/utils";
2
+ import { Parser } from "htmlparser2";
3
+ import React from "react";
4
+ /**
5
+ * Maps HTML attribute names to their React prop equivalents.
6
+ *
7
+ * React uses camelCase for most DOM attributes, but a handful of common
8
+ * HTML attributes have specific React names that do not follow the general
9
+ * camelCase rule. This dictionary covers those exceptions. Any attribute
10
+ * not listed here falls through to the generic `toCamelCase` converter.
11
+ */
12
+ const REACT_KEYS_DICTIONARY = {
13
+ class: "className",
14
+ for: "htmlFor",
15
+ crossorigin: "crossOrigin",
16
+ charset: "charSet",
17
+ };
18
+ /** Tag names that `getOtherHeadElements` collects. */
19
+ const HEAD_OTHER_TAGS = new Set(["title", "style", "meta", "base"]);
20
+ /**
21
+ * Parse an HTML string and extract all `<head>` elements of interest.
22
+ *
23
+ * Uses htmlparser2's SAX `Parser` to stream through the HTML in a single
24
+ * pass. Each opening tag that matches a head element type is recorded,
25
+ * and any immediate text content is captured for tags like `<title>` and
26
+ * `<style>` that carry inline text.
27
+ *
28
+ * Elements are returned in document order, grouped into three categories:
29
+ * links, scripts, and everything else (title, style, meta, base).
30
+ *
31
+ * @param html - The full HTML string to parse (typically from a Vite build).
32
+ * @returns Three arrays of collected elements, in document order.
33
+ */
34
+ function parseHeadElements(html) {
35
+ const links = [];
36
+ const scripts = [];
37
+ const others = [];
38
+ let current;
39
+ const parser = new Parser({
40
+ onopentag(name, attribs) {
41
+ if (name === "link" || name === "script" || HEAD_OTHER_TAGS.has(name)) {
42
+ current = { name, attribs, text: undefined };
43
+ }
44
+ else {
45
+ current = undefined;
46
+ }
47
+ },
48
+ ontext(data) {
49
+ if (current) {
50
+ current.text = current.text == null ? data : current.text + data;
51
+ }
52
+ },
53
+ onclosetag(name) {
54
+ if (!current || current.name !== name)
55
+ return;
56
+ // <link> is void — it never has text children
57
+ if (name === "link") {
58
+ current.text = undefined;
59
+ }
60
+ if (current.name === "link") {
61
+ links.push(current);
62
+ }
63
+ else if (current.name === "script") {
64
+ scripts.push(current);
65
+ }
66
+ else {
67
+ others.push(current);
68
+ }
69
+ current = undefined;
70
+ },
71
+ });
72
+ parser.write(html);
73
+ parser.end();
74
+ // Flush any pending <link> that never received an onclosetag.
75
+ // htmlparser2 fires onclosetag for void elements, so this is a defensive
76
+ // guard for edge cases in non-standard HTML or alternative parsers.
77
+ /* v8 ignore next 4 -- defensive: htmlparser2 always fires onclosetag for void elements */
78
+ if (current?.name === "link") {
79
+ current.text = undefined;
80
+ links.push(current);
81
+ }
82
+ return { links, scripts, others };
83
+ }
84
+ /**
85
+ * Extracts `<head>` elements from an HTML string and converts them to React elements.
86
+ *
87
+ * The primary use case is server-side rendering with Vite: the build process produces
88
+ * an HTML shell containing `<script>`, `<link>`, `<meta>`, `<title>`, `<style>`, and
89
+ * `<base>` tags. This class parses that shell via `parseHeadElements` (a SAX handler —
90
+ * no DOM tree is constructed) and exposes the extracted tags as React elements that the
91
+ * server entrypoint component can inject into its rendered output.
92
+ *
93
+ * Parsing happens once in the constructor. The three getter methods return the
94
+ * elements in the order they appeared in the original HTML.
95
+ */
96
+ export default class Extractor {
97
+ /** Parsed head elements, grouped by category. */
98
+ parsed;
99
+ /**
100
+ * Create an Extractor for the given HTML string.
101
+ *
102
+ * @param html - The full HTML string to parse (typically from a Vite build).
103
+ */
104
+ constructor(html) {
105
+ this.parsed = parseHeadElements(html);
106
+ }
107
+ /**
108
+ * Convert an HTML attribute name to the corresponding React prop name.
109
+ *
110
+ * Checks `REACT_KEYS_DICTIONARY` for known exceptions (e.g. `class` becomes
111
+ * `className`). Falls back to generic camelCase conversion, which correctly
112
+ * handles `data-*` and `aria-*` attributes.
113
+ *
114
+ * @param key - The HTML attribute name, e.g. `"crossorigin"` or `"data-test-id"`.
115
+ * @returns The React prop name, e.g. `"crossOrigin"` or `"dataTestId"`.
116
+ */
117
+ convertKeyToReactKey(key) {
118
+ return REACT_KEYS_DICTIONARY[key.toLowerCase()] ?? toCamelCase(key);
119
+ }
120
+ /**
121
+ * Convert a collected element into a `React.createElement` call.
122
+ *
123
+ * Attributes are mapped to React prop names via `convertKeyToReactKey`.
124
+ * A stable `key` prop is synthesised from the tag name and the element's
125
+ * position index so that React can reconcile lists of head elements.
126
+ *
127
+ * If the element has text content (e.g. `<title>My App</title>` or
128
+ * `<style>.body { color: red }</style>`), it is passed as the element's
129
+ * `children` argument.
130
+ *
131
+ * @param element - The collected element data from SAX parsing.
132
+ * @param index - The position of this element within its sibling group.
133
+ * @returns A React element matching the original HTML tag.
134
+ */
135
+ convertToReactElement(element, index) {
136
+ const props = {};
137
+ for (const [key, value] of Object.entries(element.attribs)) {
138
+ props[this.convertKeyToReactKey(key)] = value;
139
+ }
140
+ props.key = `${element.name}_${index}`;
141
+ return React.createElement(element.name, props, element.text);
142
+ }
143
+ /**
144
+ * Return all `<link>` elements as React elements, in document order.
145
+ *
146
+ * Typically used to inject stylesheet and preload links into the
147
+ * server-rendered `<head>`.
148
+ */
149
+ getLinkElements() {
150
+ return this.parsed.links.map((el, i) => this.convertToReactElement(el, i));
151
+ }
152
+ /**
153
+ * Return all `<script>` elements as React elements, in document order.
154
+ *
155
+ * Preserving order is important: Vite dev mode emits module scripts
156
+ * that depend on being evaluated in sequence.
157
+ */
158
+ getScriptElements() {
159
+ return this.parsed.scripts.map((el, i) => this.convertToReactElement(el, i));
160
+ }
161
+ /**
162
+ * Return all non-script, non-link head elements as React elements, in document order.
163
+ *
164
+ * This covers `<title>`, `<style>`, `<meta>`, and `<base>`. Elements are returned
165
+ * in the order they appeared in the HTML, preserving inter-type ordering (a `<meta>`
166
+ * between two `<style>` tags stays between them).
167
+ */
168
+ getOtherHeadElements() {
169
+ return this.parsed.others.map((el, i) => this.convertToReactElement(el, i));
170
+ }
171
+ }
172
+ //# sourceMappingURL=Extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Extractor.js","sourceRoot":"","sources":["../../../../src/lib/renderer/Extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,283 @@
1
+ import { createElement } from "react";
2
+ import { renderToPipeableStream as reactRenderToPipeableStream, renderToReadableStream as reactRenderToReadableStream, renderToString as reactRenderToString, } from "react-dom/server";
3
+ import { INITIAL_DATA_KEY } from "./constants.js";
4
+ import Extractor from "./Extractor.js";
5
+ /**
6
+ * Server-side renderer for a React component.
7
+ *
8
+ * Accepts a React `ServerEntrypoint` component, optional initial data for
9
+ * hydration, and an optional HTML shell string (from a Vite build) whose
10
+ * `<head>` tags are extracted and injected into the rendered output.
11
+ *
12
+ * Three rendering strategies are available:
13
+ *
14
+ * - **ReadableStream** (`renderToReadableStream`) — returns a web `ReadableStream`.
15
+ * Works natively with Bun, Deno, Cloudflare Workers, and any runtime that
16
+ * supports the Web Streams API. Supports Suspense and progressive rendering.
17
+ *
18
+ * - **PipeableStream** (`renderToPipeableStream`) — returns a Node.js pipeable stream.
19
+ * Works with Express, Fastify, and Node's built-in `http` module. Supports Suspense
20
+ * and progressive rendering.
21
+ *
22
+ * - **String** (`renderToString`) — returns the full HTML as a string. All Suspense
23
+ * boundaries resolve synchronously. The output is cacheable and works with Vite
24
+ * HMR in dev mode.
25
+ *
26
+ * All strategies inject `<script>` and `<link>` tags from the HTML shell via
27
+ * React's `bootstrapScripts` / `bootstrapModules` mechanism, and embed
28
+ * `initialData` as a global `window.__INITIAL_DATA__` variable for client
29
+ * hydration.
30
+ *
31
+ * The renderer is transport-agnostic — it never writes to a response object.
32
+ * HTTP status codes and metadata are exposed via `statusCode` and `statusReady`
33
+ * for the consumer to use when constructing the response.
34
+ *
35
+ * @typeParam TComponent - The server entrypoint component type.
36
+ * @typeParam InitialData - Shape of the data embedded for client hydration.
37
+ */
38
+ export default class JSXRenderer {
39
+ Component;
40
+ initialData;
41
+ options;
42
+ extractor;
43
+ /**
44
+ * HTTP status code determined during rendering.
45
+ *
46
+ * Starts at 200 and is set to 500 if a shell error occurs during streaming.
47
+ * For `renderToString`, it is always 200 (errors throw instead).
48
+ *
49
+ * Read this after the render method returns (for `renderToReadableStream` and
50
+ * `renderToString`) or after awaiting `statusReady` (for `renderToPipeableStream`).
51
+ */
52
+ statusCode = 200;
53
+ /**
54
+ * Resolves when `statusCode` is determined.
55
+ *
56
+ * For `renderToReadableStream` and `renderToString`, this is already resolved
57
+ * by the time the method returns. For `renderToPipeableStream`, it resolves
58
+ * asynchronously when the shell is ready or errors.
59
+ */
60
+ statusReady = Promise.resolve();
61
+ /**
62
+ * Create a renderer bound to a specific component and initial data.
63
+ *
64
+ * If `options.htmlString` is provided, the HTML is parsed once to extract
65
+ * `<head>` elements. These elements are then available as React elements
66
+ * via `getComponentProps()` for injection during rendering.
67
+ *
68
+ * @param Component - The React server entrypoint component.
69
+ * @param initialData - Data to embed in `window.__INITIAL_DATA__` for client hydration.
70
+ * @param options - Renderer configuration: locale, HTML shell, and stream options.
71
+ */
72
+ constructor(Component, initialData = {}, options = {}) {
73
+ this.Component = Component;
74
+ this.initialData = initialData;
75
+ this.options = options;
76
+ this.extractor = this.options.htmlString
77
+ ? new Extractor(this.options.htmlString)
78
+ : undefined;
79
+ }
80
+ /**
81
+ * Return the locale for the rendered page.
82
+ *
83
+ * Defaults to `"en"` when no `defaultLocale` was provided in options.
84
+ * The locale is passed as the `lang` prop to the server entrypoint component,
85
+ * which typically sets it as the `<html lang>` attribute.
86
+ */
87
+ getLocale() {
88
+ return this.options.defaultLocale || "en";
89
+ }
90
+ /**
91
+ * Assemble the props passed to the server entrypoint component.
92
+ *
93
+ * Combines the locale, initial data, and (when an HTML shell was provided)
94
+ * the extracted script, link, and other head elements into a single props
95
+ * object conforming to `ServerEntrypointProps`.
96
+ */
97
+ getComponentProps() {
98
+ return {
99
+ lang: this.getLocale(),
100
+ scriptElements: this.extractor?.getScriptElements(),
101
+ linkElements: this.extractor?.getLinkElements(),
102
+ otherHeadElements: this.extractor?.getOtherHeadElements(),
103
+ initialData: this.initialData,
104
+ };
105
+ }
106
+ /**
107
+ * Extract `src` URLs from script elements that match a given loading strategy.
108
+ *
109
+ * Filters the provided React `<script>` elements by their `type` attribute:
110
+ * `"module"` selects ES module scripts, `"classic"` selects everything else.
111
+ * Returns only the `src` values, discarding inline scripts that have no `src`.
112
+ *
113
+ * @param scripts - React elements representing `<script>` tags.
114
+ * @param type - `"module"` for ES modules, `"classic"` for traditional scripts.
115
+ * @returns An array of script source URLs.
116
+ */
117
+ getScriptSourcesByType(scripts, type) {
118
+ return scripts
119
+ .map((script) => script)
120
+ .filter((script) => {
121
+ if (type === "module") {
122
+ return script.props.type === "module";
123
+ }
124
+ return script.props.type !== "module";
125
+ })
126
+ .map((script) => script.props.src)
127
+ .filter((src) => typeof src === "string");
128
+ }
129
+ /**
130
+ * Merge renderer-managed options into the user-provided stream options.
131
+ *
132
+ * Populates three React streaming options unless the caller already supplied them:
133
+ *
134
+ * - `bootstrapScriptContent` — a `<script>` body that assigns `initialData` to
135
+ * `window.__INITIAL_DATA__`. The JSON is escaped to prevent `</script>` injection.
136
+ * - `bootstrapScripts` — `src` URLs for classic (non-module) scripts extracted from
137
+ * the HTML shell. React strips `<script>` tags during streaming, so these must
138
+ * be re-injected through this mechanism.
139
+ * - `bootstrapModules` — same as above, for ES module scripts.
140
+ *
141
+ * @param props - The assembled component props (used to read `initialData` and `scriptElements`).
142
+ * @returns A merged options object safe to pass to either streaming API.
143
+ */
144
+ enrichRendererOptions(props) {
145
+ const enrichedOptions = {
146
+ ...this.options.renderToPipeableStreamOptions,
147
+ };
148
+ if (!enrichedOptions.bootstrapScriptContent) {
149
+ if (props.initialData) {
150
+ enrichedOptions.bootstrapScriptContent = `window.${INITIAL_DATA_KEY} = ${JSON.stringify(props.initialData).replace(/</g, "\\u003c")}`;
151
+ }
152
+ }
153
+ if (!enrichedOptions.bootstrapScripts) {
154
+ if (props.scriptElements) {
155
+ enrichedOptions.bootstrapScripts = this.getScriptSourcesByType(props.scriptElements, "classic");
156
+ }
157
+ }
158
+ if (!enrichedOptions.bootstrapModules) {
159
+ if (props.scriptElements) {
160
+ enrichedOptions.bootstrapModules = this.getScriptSourcesByType(props.scriptElements, "module");
161
+ }
162
+ }
163
+ return enrichedOptions;
164
+ }
165
+ /**
166
+ * Render the component to a web `ReadableStream`.
167
+ *
168
+ * Uses `react-dom/server.renderToReadableStream` for environments that support
169
+ * the Web Streams API (Bun, Deno, Cloudflare Workers, browsers). Supports
170
+ * Suspense and progressive rendering.
171
+ *
172
+ * On shell error, `statusCode` is set to 500 and a fallback HTML stream is
173
+ * returned. On success, `statusCode` is 200.
174
+ *
175
+ * @note This method is impure — it mutates `statusCode` and `statusReady`.
176
+ *
177
+ * @param signal - Optional `AbortSignal` for request cancellation.
178
+ * @returns A `ReadableStream` of the rendered HTML.
179
+ */
180
+ renderToReadableStream = async (signal) => {
181
+ const props = this.getComponentProps();
182
+ const jsx = createElement(this.Component, props);
183
+ const { onError: onErrorCallback,
184
+ // Strip pipeable-only callbacks — they don't exist on RenderToReadableStreamOptions
185
+ onShellReady: _onShellReady, onShellError: _onShellError, onAllReady: _onAllReady, ...options } = this.enrichRendererOptions(props);
186
+ try {
187
+ const stream = await reactRenderToReadableStream(jsx, {
188
+ ...options,
189
+ ...(this.options.renderToReadableStreamOptions ?? {}),
190
+ signal,
191
+ onError: (error, errorInfo) => {
192
+ onErrorCallback?.(error, errorInfo);
193
+ console.error(error);
194
+ },
195
+ });
196
+ this.statusCode = 200;
197
+ this.statusReady = Promise.resolve();
198
+ return stream;
199
+ }
200
+ catch (error) {
201
+ console.error(error);
202
+ this.statusCode = 500;
203
+ this.statusReady = Promise.resolve();
204
+ return new ReadableStream({
205
+ start(controller) {
206
+ controller.enqueue(new TextEncoder().encode("<h1>Something went wrong</h1>"));
207
+ controller.close();
208
+ },
209
+ });
210
+ }
211
+ };
212
+ /**
213
+ * Render the component to a Node.js pipeable stream.
214
+ *
215
+ * Uses `react-dom/server.renderToPipeableStream` for Node.js environments
216
+ * (Express, Fastify, plain `http.createServer`). Supports Suspense and
217
+ * progressive rendering.
218
+ *
219
+ * Returns `{ pipe, abort }` synchronously. The `statusCode` is set
220
+ * asynchronously when the shell is ready or errors — await `statusReady`
221
+ * before reading it.
222
+ *
223
+ * @note This method is impure — it mutates `statusCode` and `statusReady`.
224
+ *
225
+ * @returns The pipe/abort handles for the rendered stream.
226
+ */
227
+ renderToPipeableStream = () => {
228
+ const props = this.getComponentProps();
229
+ const jsx = createElement(this.Component, props);
230
+ const { onShellError: onShellErrorCallback, onShellReady: onShellReadyCallback, onAllReady: onAllReadyCallback, onError: onErrorCallback, ...options } = this.enrichRendererOptions(props);
231
+ const errorRef = { current: undefined };
232
+ let resolveStatus;
233
+ this.statusReady = new Promise((resolve) => {
234
+ resolveStatus = resolve;
235
+ });
236
+ const jsxStream = reactRenderToPipeableStream(jsx, {
237
+ ...options,
238
+ onError(error, errorInfo) {
239
+ onErrorCallback?.(error, errorInfo);
240
+ errorRef.current = error;
241
+ console.error(error);
242
+ },
243
+ onShellError: (error) => {
244
+ onShellErrorCallback?.(error);
245
+ this.statusCode = 500;
246
+ resolveStatus();
247
+ console.error(error);
248
+ },
249
+ onShellReady: () => {
250
+ onShellReadyCallback?.();
251
+ /* v8 ignore next -- errorRef.current is set by onError which fires before onShellReady in edge cases */
252
+ this.statusCode = errorRef.current ? 500 : 200;
253
+ resolveStatus();
254
+ },
255
+ onAllReady() {
256
+ onAllReadyCallback?.();
257
+ },
258
+ });
259
+ return { pipe: jsxStream.pipe, abort: jsxStream.abort };
260
+ };
261
+ /**
262
+ * Render the component to a complete HTML string.
263
+ *
264
+ * Uses `react-dom/server.renderToString` to produce the full HTML
265
+ * synchronously. All Suspense boundaries resolve before the method returns.
266
+ * The output is cacheable and compatible with Vite's HMR in dev mode.
267
+ *
268
+ * Sets `statusCode` to 200 on success. On error, throws (consumer catches).
269
+ *
270
+ * @note This method is impure — it mutates `statusCode`.
271
+ *
272
+ * @returns The complete HTML string.
273
+ */
274
+ renderToString = () => {
275
+ const props = this.getComponentProps();
276
+ const jsx = createElement(this.Component, props);
277
+ const html = reactRenderToString(jsx);
278
+ this.statusCode = 200;
279
+ this.statusReady = Promise.resolve();
280
+ return html;
281
+ };
282
+ }
283
+ //# sourceMappingURL=JSXRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JSXRenderer.js","sourceRoot":"","sources":["../../../../src/lib/renderer/JSXRenderer.ts"],"names":[],"mappings":"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"}