@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.
- package/dist/esm/bin/serve-express.js +86 -0
- package/dist/esm/bin/serve-express.js.map +1 -0
- package/dist/esm/index.js +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/index.js +3 -0
- package/dist/esm/lib/index.js.map +1 -0
- package/dist/esm/lib/renderer/Extractor.js +172 -0
- package/dist/esm/lib/renderer/Extractor.js.map +1 -0
- package/dist/esm/lib/renderer/JSXRenderer.js +283 -0
- package/dist/esm/lib/renderer/JSXRenderer.js.map +1 -0
- package/dist/esm/lib/renderer/SitemapRenderer.js +241 -0
- package/dist/esm/lib/renderer/SitemapRenderer.js.map +1 -0
- package/dist/esm/lib/renderer/TextRenderer.js +124 -0
- package/dist/esm/lib/renderer/TextRenderer.js.map +1 -0
- package/dist/esm/lib/renderer/constants.js.map +1 -0
- package/dist/esm/lib/renderer/index.js +6 -0
- package/dist/esm/lib/renderer/index.js.map +1 -0
- package/dist/esm/lib/renderer/types.js +10 -0
- package/dist/esm/lib/renderer/types.js.map +1 -0
- package/dist/esm/lib/server/index.js +3 -0
- package/dist/esm/lib/server/index.js.map +1 -0
- package/dist/esm/lib/server/serveStream.js +53 -0
- package/dist/esm/lib/server/serveStream.js.map +1 -0
- package/dist/esm/lib/server/serveString.js +49 -0
- package/dist/esm/lib/server/serveString.js.map +1 -0
- package/dist/types/bin/serve-express.d.ts +24 -0
- package/dist/types/bin/serve-express.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/index.d.ts +3 -0
- package/dist/types/lib/index.d.ts.map +1 -0
- package/dist/types/lib/renderer/Extractor.d.ts +93 -0
- package/dist/types/lib/renderer/Extractor.d.ts.map +1 -0
- package/dist/types/lib/renderer/JSXRenderer.d.ts +163 -0
- package/dist/types/lib/renderer/JSXRenderer.d.ts.map +1 -0
- package/dist/types/lib/renderer/SitemapRenderer.d.ts +153 -0
- package/dist/types/lib/renderer/SitemapRenderer.d.ts.map +1 -0
- package/dist/types/lib/renderer/TextRenderer.d.ts +83 -0
- package/dist/types/lib/renderer/TextRenderer.d.ts.map +1 -0
- package/dist/types/lib/renderer/constants.d.ts.map +1 -0
- package/dist/types/lib/renderer/index.d.ts +7 -0
- package/dist/types/lib/renderer/index.d.ts.map +1 -0
- package/dist/types/lib/renderer/types.d.ts +161 -0
- package/dist/types/lib/renderer/types.d.ts.map +1 -0
- package/dist/types/lib/server/index.d.ts +3 -0
- package/dist/types/lib/server/index.d.ts.map +1 -0
- package/dist/types/lib/server/serveStream.d.ts +41 -0
- package/dist/types/lib/server/serveStream.d.ts.map +1 -0
- package/dist/types/lib/server/serveString.d.ts +37 -0
- package/dist/types/lib/server/serveString.d.ts.map +1 -0
- package/package.json +32 -17
- package/dist/esm/renderer/Extractor.js +0 -127
- package/dist/esm/renderer/Extractor.js.map +0 -1
- package/dist/esm/renderer/JSXRenderer.js +0 -168
- package/dist/esm/renderer/JSXRenderer.js.map +0 -1
- package/dist/esm/renderer/constants.js.map +0 -1
- package/dist/esm/renderer/index.js +0 -4
- package/dist/esm/renderer/index.js.map +0 -1
- package/dist/esm/renderer/types.js +0 -2
- package/dist/esm/renderer/types.js.map +0 -1
- package/dist/esm/server/index.js +0 -2
- package/dist/esm/server/index.js.map +0 -1
- package/dist/esm/server/serve-express.js +0 -58
- package/dist/esm/server/serve-express.js.map +0 -1
- package/dist/esm/server/serve.js +0 -41
- package/dist/esm/server/serve.js.map +0 -1
- package/dist/types/renderer/Extractor.d.ts +0 -68
- package/dist/types/renderer/Extractor.d.ts.map +0 -1
- package/dist/types/renderer/JSXRenderer.d.ts +0 -71
- package/dist/types/renderer/JSXRenderer.d.ts.map +0 -1
- package/dist/types/renderer/constants.d.ts.map +0 -1
- package/dist/types/renderer/index.d.ts +0 -5
- package/dist/types/renderer/index.d.ts.map +0 -1
- package/dist/types/renderer/types.d.ts +0 -35
- package/dist/types/renderer/types.d.ts.map +0 -1
- package/dist/types/server/index.d.ts +0 -2
- package/dist/types/server/index.d.ts.map +0 -1
- package/dist/types/server/serve-express.d.ts +0 -3
- package/dist/types/server/serve-express.d.ts.map +0 -1
- package/dist/types/server/serve.d.ts +0 -30
- package/dist/types/server/serve.d.ts.map +0 -1
- /package/dist/esm/{renderer → lib/renderer}/constants.js +0 -0
- /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
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,
|
|
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 @@
|
|
|
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":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;;;;;GAOG;AACH,MAAM,qBAAqB,GAA2B;IACpD,KAAK,EAAE,WAAW;IAClB,GAAG,EAAE,SAAS;IACd,WAAW,EAAE,aAAa;IAC1B,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF,sDAAsD;AACtD,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAuBpE;;;;;;;;;;;;;GAaG;AACH,SAAS,iBAAiB,CAAC,IAAY;IACrC,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,MAAM,MAAM,GAAuB,EAAE,CAAC;IAEtC,IAAI,OAAqC,CAAC;IAE1C,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,SAAS,CAAC,IAAI,EAAE,OAAO;YACrB,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,QAAQ,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtE,OAAO,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACN,OAAO,GAAG,SAAS,CAAC;YACtB,CAAC;QACH,CAAC;QACD,MAAM,CAAC,IAAI;YACT,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;YACnE,CAAC;QACH,CAAC;QACD,UAAU,CAAC,IAAI;YACb,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI;gBAAE,OAAO;YAE9C,8CAA8C;YAC9C,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpB,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;YAC3B,CAAC;YAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;YAED,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,MAAM,CAAC,GAAG,EAAE,CAAC;IAEb,8DAA8D;IAC9D,yEAAyE;IACzE,oEAAoE;IACpE,0FAA0F;IAC1F,IAAI,OAAO,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,OAAO,OAAO,SAAS;IAC5B,iDAAiD;IAC9B,MAAM,CAAc;IAEvC;;;;OAIG;IACH,YAAY,IAAY;QACtB,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IAED;;;;;;;;;OASG;IACO,oBAAoB,CAAC,GAAW;QACxC,OAAO,qBAAqB,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC;IACtE,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACO,qBAAqB,CAC7B,OAAyB,EACzB,KAAa;QAEb,MAAM,KAAK,GAA2B,EAAE,CAAC;QAEzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3D,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;QAChD,CAAC;QAED,KAAK,CAAC,GAAG,GAAG,GAAG,OAAO,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,CAAC;IAED;;;;;OAKG;IACI,eAAe;QACpB,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED;;;;;OAKG;IACI,iBAAiB;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CACvC,IAAI,CAAC,qBAAqB,CAAC,EAAE,EAAE,CAAC,CAAC,CAClC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACI,oBAAoB;QACzB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;CACF"}
|
|
@@ -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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"}
|