@canonical/react-ssr 0.22.0 → 0.24.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 (95) 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 +3 -2
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/esm/lib/adapter/index.js +2 -0
  6. package/dist/esm/lib/adapter/index.js.map +1 -0
  7. package/dist/esm/lib/adapter/mime.js +85 -0
  8. package/dist/esm/lib/adapter/mime.js.map +1 -0
  9. package/dist/esm/lib/adapter/types.js +10 -0
  10. package/dist/esm/lib/adapter/types.js.map +1 -0
  11. package/dist/esm/lib/index.js +4 -0
  12. package/dist/esm/lib/index.js.map +1 -0
  13. package/dist/esm/lib/renderer/Extractor.js +172 -0
  14. package/dist/esm/lib/renderer/Extractor.js.map +1 -0
  15. package/dist/esm/lib/renderer/JSXRenderer.js +283 -0
  16. package/dist/esm/lib/renderer/JSXRenderer.js.map +1 -0
  17. package/dist/esm/lib/renderer/SitemapRenderer.js +241 -0
  18. package/dist/esm/lib/renderer/SitemapRenderer.js.map +1 -0
  19. package/dist/esm/lib/renderer/TextRenderer.js +124 -0
  20. package/dist/esm/lib/renderer/TextRenderer.js.map +1 -0
  21. package/dist/esm/lib/renderer/constants.js.map +1 -0
  22. package/dist/esm/lib/renderer/index.js +6 -0
  23. package/dist/esm/lib/renderer/index.js.map +1 -0
  24. package/dist/esm/lib/renderer/types.js +10 -0
  25. package/dist/esm/lib/renderer/types.js.map +1 -0
  26. package/dist/esm/lib/server/index.js +3 -0
  27. package/dist/esm/lib/server/index.js.map +1 -0
  28. package/dist/esm/lib/server/serveStream.js +53 -0
  29. package/dist/esm/lib/server/serveStream.js.map +1 -0
  30. package/dist/esm/lib/server/serveString.js +49 -0
  31. package/dist/esm/lib/server/serveString.js.map +1 -0
  32. package/dist/types/bin/serve-express.d.ts +24 -0
  33. package/dist/types/bin/serve-express.d.ts.map +1 -0
  34. package/dist/types/index.d.ts +3 -2
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/dist/types/lib/adapter/index.d.ts +3 -0
  37. package/dist/types/lib/adapter/index.d.ts.map +1 -0
  38. package/dist/types/lib/adapter/mime.d.ts +43 -0
  39. package/dist/types/lib/adapter/mime.d.ts.map +1 -0
  40. package/dist/types/lib/adapter/types.d.ts +85 -0
  41. package/dist/types/lib/adapter/types.d.ts.map +1 -0
  42. package/dist/types/lib/index.d.ts +4 -0
  43. package/dist/types/lib/index.d.ts.map +1 -0
  44. package/dist/types/lib/renderer/Extractor.d.ts +93 -0
  45. package/dist/types/lib/renderer/Extractor.d.ts.map +1 -0
  46. package/dist/types/lib/renderer/JSXRenderer.d.ts +163 -0
  47. package/dist/types/lib/renderer/JSXRenderer.d.ts.map +1 -0
  48. package/dist/types/lib/renderer/SitemapRenderer.d.ts +153 -0
  49. package/dist/types/lib/renderer/SitemapRenderer.d.ts.map +1 -0
  50. package/dist/types/lib/renderer/TextRenderer.d.ts +83 -0
  51. package/dist/types/lib/renderer/TextRenderer.d.ts.map +1 -0
  52. package/dist/types/lib/renderer/constants.d.ts.map +1 -0
  53. package/dist/types/lib/renderer/index.d.ts +7 -0
  54. package/dist/types/lib/renderer/index.d.ts.map +1 -0
  55. package/dist/types/lib/renderer/types.d.ts +161 -0
  56. package/dist/types/lib/renderer/types.d.ts.map +1 -0
  57. package/dist/types/lib/server/index.d.ts +3 -0
  58. package/dist/types/lib/server/index.d.ts.map +1 -0
  59. package/dist/types/lib/server/serveStream.d.ts +41 -0
  60. package/dist/types/lib/server/serveStream.d.ts.map +1 -0
  61. package/dist/types/lib/server/serveString.d.ts +37 -0
  62. package/dist/types/lib/server/serveString.d.ts.map +1 -0
  63. package/package.json +37 -17
  64. package/dist/esm/renderer/Extractor.js +0 -127
  65. package/dist/esm/renderer/Extractor.js.map +0 -1
  66. package/dist/esm/renderer/JSXRenderer.js +0 -168
  67. package/dist/esm/renderer/JSXRenderer.js.map +0 -1
  68. package/dist/esm/renderer/constants.js.map +0 -1
  69. package/dist/esm/renderer/index.js +0 -4
  70. package/dist/esm/renderer/index.js.map +0 -1
  71. package/dist/esm/renderer/types.js +0 -2
  72. package/dist/esm/renderer/types.js.map +0 -1
  73. package/dist/esm/server/index.js +0 -2
  74. package/dist/esm/server/index.js.map +0 -1
  75. package/dist/esm/server/serve-express.js +0 -58
  76. package/dist/esm/server/serve-express.js.map +0 -1
  77. package/dist/esm/server/serve.js +0 -41
  78. package/dist/esm/server/serve.js.map +0 -1
  79. package/dist/types/renderer/Extractor.d.ts +0 -68
  80. package/dist/types/renderer/Extractor.d.ts.map +0 -1
  81. package/dist/types/renderer/JSXRenderer.d.ts +0 -71
  82. package/dist/types/renderer/JSXRenderer.d.ts.map +0 -1
  83. package/dist/types/renderer/constants.d.ts.map +0 -1
  84. package/dist/types/renderer/index.d.ts +0 -5
  85. package/dist/types/renderer/index.d.ts.map +0 -1
  86. package/dist/types/renderer/types.d.ts +0 -35
  87. package/dist/types/renderer/types.d.ts.map +0 -1
  88. package/dist/types/server/index.d.ts +0 -2
  89. package/dist/types/server/index.d.ts.map +0 -1
  90. package/dist/types/server/serve-express.d.ts +0 -3
  91. package/dist/types/server/serve-express.d.ts.map +0 -1
  92. package/dist/types/server/serve.d.ts +0 -30
  93. package/dist/types/server/serve.d.ts.map +0 -1
  94. /package/dist/esm/{renderer → lib/renderer}/constants.js +0 -0
  95. /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,4 @@
1
- export * as renderer from "./renderer/index.js";
2
- export * as server from "./server/index.js";
1
+ export * as adapter from "./lib/adapter/index.js";
2
+ export * as renderer from "./lib/renderer/index.js";
3
+ export * as server from "./lib/server/index.js";
3
4
  //# 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,OAAO,MAAM,wBAAwB,CAAC;AAClD,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { buildCacheControl, getMimeType, matchPattern } from "./mime.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/adapter/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Minimal MIME type lookup for static asset serving.
3
+ *
4
+ * Covers the file types commonly produced by Vite builds (scripts, styles,
5
+ * images, fonts, source maps). Adapters use this when the platform doesn't
6
+ * provide native MIME detection (Cloudflare R2, Deno filesystem).
7
+ *
8
+ * No external dependencies — the lookup is a plain record.
9
+ */
10
+ const MIME_TYPES = {
11
+ ".html": "text/html; charset=utf-8",
12
+ ".js": "application/javascript; charset=utf-8",
13
+ ".mjs": "application/javascript; charset=utf-8",
14
+ ".css": "text/css; charset=utf-8",
15
+ ".json": "application/json; charset=utf-8",
16
+ ".svg": "image/svg+xml",
17
+ ".png": "image/png",
18
+ ".jpg": "image/jpeg",
19
+ ".jpeg": "image/jpeg",
20
+ ".gif": "image/gif",
21
+ ".webp": "image/webp",
22
+ ".avif": "image/avif",
23
+ ".ico": "image/x-icon",
24
+ ".woff": "font/woff",
25
+ ".woff2": "font/woff2",
26
+ ".ttf": "font/ttf",
27
+ ".otf": "font/otf",
28
+ ".txt": "text/plain; charset=utf-8",
29
+ ".xml": "application/xml; charset=utf-8",
30
+ ".wasm": "application/wasm",
31
+ ".map": "application/json; charset=utf-8",
32
+ };
33
+ /**
34
+ * Look up the MIME type for a file path based on its extension.
35
+ *
36
+ * Returns `"application/octet-stream"` for unknown extensions.
37
+ *
38
+ * @param path - File path or name (e.g. `"main.abc123.js"`, `"/assets/logo.png"`).
39
+ * @returns The MIME type string.
40
+ */
41
+ export function getMimeType(path) {
42
+ const dotIndex = path.lastIndexOf(".");
43
+ if (dotIndex === -1)
44
+ return "application/octet-stream";
45
+ const ext = path.slice(dotIndex).toLowerCase();
46
+ return MIME_TYPES[ext] ?? "application/octet-stream";
47
+ }
48
+ /**
49
+ * Build a `Cache-Control` header value from a cache configuration.
50
+ *
51
+ * @param cache - Cache configuration with optional directives.
52
+ * @returns A `Cache-Control` header string.
53
+ */
54
+ export function buildCacheControl(cache) {
55
+ const parts = [];
56
+ if (cache.maxAge != null)
57
+ parts.push(`max-age=${cache.maxAge}`);
58
+ if (cache.sMaxAge != null)
59
+ parts.push(`s-maxage=${cache.sMaxAge}`);
60
+ if (cache.staleWhileRevalidate != null)
61
+ parts.push(`stale-while-revalidate=${cache.staleWhileRevalidate}`);
62
+ return parts.length > 0 ? `public, ${parts.join(", ")}` : "public";
63
+ }
64
+ /**
65
+ * Match a URL pathname against a simple pattern.
66
+ *
67
+ * Supports three forms:
68
+ * - Exact match: `"/sitemap.xml"` matches only `"/sitemap.xml"`
69
+ * - Wildcard suffix: `"/api/*"` matches `"/api/"` and anything below
70
+ * - Catch-all: `"/*"` matches everything
71
+ *
72
+ * @param pattern - The route pattern.
73
+ * @param pathname - The URL pathname to test.
74
+ * @returns `true` if the pathname matches the pattern.
75
+ */
76
+ export function matchPattern(pattern, pathname) {
77
+ if (pattern === "/*")
78
+ return true;
79
+ if (pattern.endsWith("/*")) {
80
+ const prefix = pattern.slice(0, -2);
81
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
82
+ }
83
+ return pathname === pattern;
84
+ }
85
+ //# sourceMappingURL=mime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.js","sourceRoot":"","sources":["../../../../src/lib/adapter/mime.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,UAAU,GAA2B;IACzC,OAAO,EAAE,0BAA0B;IACnC,KAAK,EAAE,uCAAuC;IAC9C,MAAM,EAAE,uCAAuC;IAC/C,MAAM,EAAE,yBAAyB;IACjC,OAAO,EAAE,iCAAiC;IAC1C,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,cAAc;IACtB,OAAO,EAAE,WAAW;IACpB,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,2BAA2B;IACnC,MAAM,EAAE,gCAAgC;IACxC,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,iCAAiC;CAC1C,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,QAAQ,KAAK,CAAC,CAAC;QAAE,OAAO,0BAA0B,CAAC;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/C,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AACvD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAIjC;IACC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,CAAC,MAAM,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAChE,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACnE,IAAI,KAAK,CAAC,oBAAoB,IAAI,IAAI;QACpC,KAAK,CAAC,IAAI,CAAC,0BAA0B,KAAK,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACrE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,QAAgB;IAC5D,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACpC,OAAO,QAAQ,KAAK,MAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,QAAQ,KAAK,OAAO,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared type contracts for SSR deployment adapters.
3
+ *
4
+ * These types define the interface between platform-specific adapters
5
+ * (Cloudflare Workers, Vercel, Deno Deploy) and the transport-agnostic
6
+ * renderers in `@canonical/react-ssr`. Adapters consume these types;
7
+ * renderers satisfy them.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/lib/adapter/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,4 @@
1
+ export * as adapter from "./adapter/index.js";
2
+ export * as renderer from "./renderer/index.js";
3
+ export * as server from "./server/index.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAC;AAC9C,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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"}