@sigx/server-renderer 0.1.26 → 0.2.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.
@@ -1,20 +1,50 @@
1
- import { n as internals_exports, r as __commonJSMin } from "./types-CkI_lB93.js";
1
+ import { n as internals_exports, r as __commonJSMin } from "./types-DYlI_C8F.js";
2
2
  import { show } from "@sigx/runtime-dom";
3
3
  import { Fragment, Text, getCurrentInstance, isComponent, isDirective, signal } from "sigx";
4
+ //#region src/builtin-ssr-directives.ts
5
+ /**
6
+ * Built-in directive SSR support — lazy patching.
7
+ *
8
+ * This module patches `getSSRProps` onto built-in directives (like `show`)
9
+ * at runtime, keeping `@sigx/runtime-dom` free of SSR knowledge.
10
+ *
11
+ * Mirrors Vue 3's `initVShowForSSR()` / `initDirectivesForSSR()` pattern.
12
+ *
13
+ * @internal
14
+ */
4
15
  var _initialized = false;
16
+ /**
17
+ * Patch `getSSRProps` onto the `show` directive for SSR support.
18
+ *
19
+ * Called lazily from `initDirectivesForSSR()` — not at import time,
20
+ * so tree-shaking can eliminate this in client-only builds.
21
+ */
5
22
  function initShowForSSR() {
6
23
  show.getSSRProps = ({ value }) => {
7
24
  if (!value) return { style: { display: "none" } };
8
25
  };
9
26
  }
27
+ /**
28
+ * Initialize SSR support for all built-in directives.
29
+ *
30
+ * Must be called before any SSR rendering occurs.
31
+ * Safe to call multiple times — only patches once.
32
+ */
10
33
  function initDirectivesForSSR() {
11
34
  if (_initialized) return;
12
35
  _initialized = true;
13
36
  initShowForSSR();
14
37
  }
38
+ //#endregion
39
+ //#region __vite-browser-external
15
40
  var require___vite_browser_external = /* @__PURE__ */ __commonJSMin(((exports, module) => {
16
41
  module.exports = {};
17
42
  }));
43
+ //#endregion
44
+ //#region src/server/context.ts
45
+ /**
46
+ * Create a new SSR context for rendering
47
+ */
18
48
  function createSSRContext(options = {}) {
19
49
  let componentId = 0;
20
50
  const componentStack = [];
@@ -51,6 +81,19 @@ function createSSRContext(options = {}) {
51
81
  }
52
82
  };
53
83
  }
84
+ //#endregion
85
+ //#region src/server/render-core.ts
86
+ /**
87
+ * Core rendering logic for SSR
88
+ *
89
+ * The async generator `renderToChunks` walks a VNode tree and yields HTML strings.
90
+ * Handles text, fragments, host elements, and delegates components to the
91
+ * component renderer.
92
+ *
93
+ * This module is strategy-agnostic. Island-specific logic (signal tracking,
94
+ * hydration directives, async streaming) lives in @sigx/ssr-islands and is
95
+ * injected through the SSRPlugin hooks.
96
+ */
54
97
  var ESCAPE = {
55
98
  "&": "&",
56
99
  "<": "&lt;",
@@ -61,7 +104,9 @@ var ESCAPE = {
61
104
  function escapeHtml$1(s) {
62
105
  return s.replace(/[&<>"']/g, (c) => ESCAPE[c]);
63
106
  }
107
+ /** Cache for camelCase → kebab-case conversions (same properties repeat across elements) */
64
108
  var kebabCache = {};
109
+ /** Void elements that cannot have children — hoisted to module scope as a Set for O(1) lookup */
65
110
  var VOID_ELEMENTS = new Set([
66
111
  "area",
67
112
  "base",
@@ -82,6 +127,15 @@ function camelToKebab(str) {
82
127
  if (str.startsWith("--")) return str;
83
128
  return kebabCache[str] ||= str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
84
129
  }
130
+ /**
131
+ * Parse a CSS string into a style object.
132
+ *
133
+ * Handles edge cases: parens in values (e.g., `linear-gradient(...)`),
134
+ * CSS comments, and colons in values.
135
+ *
136
+ * Adapted from Vue 3's `parseStringStyle` — battle-tested, split-based,
137
+ * fast in V8.
138
+ */
85
139
  var listDelimiterRE = /;(?![^(]*\))/g;
86
140
  var propertyDelimiterRE = /:([^]+)/;
87
141
  var styleCommentRE = /\/\*[^]*?\*\//g;
@@ -95,6 +149,12 @@ function parseStringStyle(cssText) {
95
149
  });
96
150
  return ret;
97
151
  }
152
+ /**
153
+ * Serialize a style object to a CSS string.
154
+ *
155
+ * Uses for...in + string concat (avoids Object.entries/map/join allocations)
156
+ * and cached kebab-case conversion.
157
+ */
98
158
  function stringifyStyle(style) {
99
159
  let ret = "";
100
160
  for (const key in style) {
@@ -103,11 +163,15 @@ function stringifyStyle(style) {
103
163
  }
104
164
  return ret;
105
165
  }
166
+ /**
167
+ * Check if element will render as text content
168
+ */
106
169
  function isTextContent(element) {
107
170
  if (element == null || element === false || element === true) return false;
108
171
  if (typeof element === "string" || typeof element === "number") return true;
109
172
  return element.type === Text;
110
173
  }
174
+ /** Check if all children are leaf types (text, number, null, bool, Text vnode) */
111
175
  function allChildrenAreLeaves(children) {
112
176
  for (const child of children) {
113
177
  if (child == null || child === false || child === true) continue;
@@ -117,6 +181,11 @@ function allChildrenAreLeaves(children) {
117
181
  }
118
182
  return true;
119
183
  }
184
+ /**
185
+ * Merge style values for SSR (element style + directive SSR style).
186
+ * Either value can be an object, string, or undefined.
187
+ * String styles are parsed into objects before merging.
188
+ */
120
189
  function mergeSSRStyles(elementStyle, directiveStyle) {
121
190
  if (!elementStyle) return directiveStyle;
122
191
  if (!directiveStyle) return elementStyle;
@@ -127,6 +196,13 @@ function mergeSSRStyles(elementStyle, directiveStyle) {
127
196
  ...b
128
197
  };
129
198
  }
199
+ /**
200
+ * Render element to string chunks (generator for streaming)
201
+ * @param element - The JSX element to render
202
+ * @param ctx - The SSR context for tracking state
203
+ * @param parentCtx - The parent component context for provide/inject
204
+ * @param appContext - The app context for app-level provides (from defineApp)
205
+ */
130
206
  async function* renderToChunks(element, ctx, parentCtx = null, appContext = null) {
131
207
  if (element == null || element === false || element === true) return;
132
208
  if (typeof element === "string" || typeof element === "number") {
@@ -345,11 +421,22 @@ async function* renderToChunks(element, ctx, parentCtx = null, appContext = null
345
421
  yield `</${tagName}>`;
346
422
  }
347
423
  }
424
+ /**
425
+ * Helper to render a VNode to string (for deferred async content)
426
+ */
348
427
  async function renderVNodeToString(element, ctx, appContext = null) {
349
428
  let result = "";
350
429
  for await (const chunk of renderToChunks(element, ctx, null, appContext)) result += chunk;
351
430
  return result;
352
431
  }
432
+ /**
433
+ * Synchronous render-to-string that avoids async generator overhead.
434
+ * Returns null if any async operation is encountered (caller should fall back
435
+ * to the async generator path).
436
+ *
437
+ * For purely synchronous component trees this eliminates thousands of
438
+ * microtask/Promise allocations from the AsyncGenerator protocol.
439
+ */
353
440
  function renderToStringSync(element, ctx, parentCtx, appContext, buf) {
354
441
  if (element == null || element === false || element === true) return true;
355
442
  if (typeof element === "string" || typeof element === "number") {
@@ -528,9 +615,28 @@ function renderToStringSync(element, ctx, parentCtx, appContext, buf) {
528
615
  }
529
616
  return true;
530
617
  }
618
+ //#endregion
619
+ //#region src/server/streaming.ts
620
+ /**
621
+ * Core streaming utilities for async SSR
622
+ *
623
+ * Provides the client-side `$SIGX_REPLACE` function and replacement script
624
+ * generation used by core async streaming. These are strategy-agnostic —
625
+ * any async component with `ssr.load()` gets streamed without needing a plugin.
626
+ *
627
+ * Plugins (e.g., islands) can augment replacements via `onAsyncComponentResolved`.
628
+ */
629
+ /**
630
+ * Escape a JSON string for safe embedding inside <script> tags.
631
+ * Prevents XSS by replacing characters that could break out of the script context.
632
+ */
531
633
  function escapeJsonForScript(json) {
532
634
  return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
533
635
  }
636
+ /**
637
+ * Generate the streaming bootstrap script (injected once before any replacements).
638
+ * Defines `window.$SIGX_REPLACE` which swaps async placeholders with rendered HTML.
639
+ */
534
640
  function generateStreamingScript() {
535
641
  return `
536
642
  <script>
@@ -548,24 +654,67 @@ window.$SIGX_REPLACE = function(id, html) {
548
654
  };
549
655
  <\/script>`;
550
656
  }
657
+ /**
658
+ * Generate a replacement script for a resolved async component.
659
+ */
551
660
  function generateReplacementScript(id, html, extraScript) {
552
661
  let script = `<script>$SIGX_REPLACE(${id}, ${escapeJsonForScript(JSON.stringify(html))});`;
553
662
  if (extraScript) script += extraScript;
554
663
  script += `<\/script>`;
555
664
  return script;
556
665
  }
666
+ //#endregion
667
+ //#region src/head.ts
668
+ /**
669
+ * Head management composable for SSR and client-side.
670
+ *
671
+ * Provides `useHead()` for managing `<head>` elements (title, meta, link, script)
672
+ * from within components. Works during SSR (collects into SSRContext._head) and
673
+ * on the client (updates DOM directly).
674
+ *
675
+ * @example
676
+ * ```tsx
677
+ * import { useHead } from '@sigx/server-renderer/head';
678
+ *
679
+ * function MyPage(ctx) {
680
+ * useHead({
681
+ * title: 'My Page',
682
+ * meta: [
683
+ * { name: 'description', content: 'A great page' },
684
+ * { property: 'og:title', content: 'My Page' }
685
+ * ],
686
+ * link: [
687
+ * { rel: 'canonical', href: 'https://example.com/my-page' }
688
+ * ]
689
+ * });
690
+ *
691
+ * return () => <div>Page content</div>;
692
+ * }
693
+ * ```
694
+ */
557
695
  var _ssrHeadConfigs = [];
558
696
  var _isSSR = false;
697
+ /**
698
+ * Enable SSR mode for head management.
699
+ * Called by the SSR renderer before rendering starts.
700
+ */
559
701
  function enableSSRHead() {
560
702
  _isSSR = true;
561
703
  _ssrHeadConfigs = [];
562
704
  }
705
+ /**
706
+ * Disable SSR mode and return collected configs.
707
+ */
563
708
  function collectSSRHead() {
564
709
  _isSSR = false;
565
710
  const configs = _ssrHeadConfigs;
566
711
  _ssrHeadConfigs = [];
567
712
  return configs;
568
713
  }
714
+ /**
715
+ * Render collected head configs to an HTML string.
716
+ * Deduplicates meta tags by name/property and uses the last title.
717
+ */
569
718
  function renderHeadToString(configs) {
570
719
  const parts = [];
571
720
  const seenMeta = /* @__PURE__ */ new Map();
@@ -647,6 +796,14 @@ function applyHeadClient(config) {
647
796
  for (const el of managed) el.remove();
648
797
  };
649
798
  }
799
+ /**
800
+ * Manage `<head>` elements from within a component.
801
+ *
802
+ * During SSR, collects head configs for later rendering with `renderHeadToString()`.
803
+ * On the client, updates the DOM directly. Cleans up on component unmount.
804
+ *
805
+ * @param config - Head configuration (title, meta, link, script, etc.)
806
+ */
650
807
  function useHead(config) {
651
808
  if (_isSSR) {
652
809
  _ssrHeadConfigs.push(config);
@@ -662,10 +819,18 @@ function escapeHtml(s) {
662
819
  function escapeAttr(s) {
663
820
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
664
821
  }
822
+ //#endregion
823
+ //#region src/ssr.ts
665
824
  var import___vite_browser_external = require___vite_browser_external();
825
+ /**
826
+ * Check if the input is an App instance (created via defineApp)
827
+ */
666
828
  function isApp(input) {
667
829
  return input && typeof input === "object" && "_rootComponent" in input && "_context" in input;
668
830
  }
831
+ /**
832
+ * Extract the JSX element and optional AppContext from a render input.
833
+ */
669
834
  function extractInput(input) {
670
835
  if (isApp(input)) return {
671
836
  element: input._rootComponent,
@@ -676,6 +841,15 @@ function extractInput(input) {
676
841
  appContext: null
677
842
  };
678
843
  }
844
+ /**
845
+ * Yield all async streaming chunks — core-managed and plugin-managed — interleaved
846
+ * so the fastest component streams first regardless of who manages it.
847
+ *
848
+ * Core-managed: ctx._pendingAsync (from render-core when no plugin overrides)
849
+ * Plugin-managed: plugin.server.getStreamingChunks() async generators
850
+ *
851
+ * Both are raced together using a unified promise race loop.
852
+ */
679
853
  async function* streamAllAsyncChunks(ctx, plugins) {
680
854
  const hasCoreAsync = ctx._pendingAsync.length > 0;
681
855
  const pluginGenerators = [];
@@ -752,6 +926,9 @@ async function* streamAllAsyncChunks(ctx, plugins) {
752
926
  if (winner.index < totalCore) resolvedCore.add(winner.index);
753
927
  }
754
928
  }
929
+ /**
930
+ * Create an SSR instance with plugin support.
931
+ */
755
932
  function createSSR() {
756
933
  const plugins = [];
757
934
  function makeContext(options) {
@@ -893,20 +1070,80 @@ function createSSR() {
893
1070
  }
894
1071
  };
895
1072
  }
1073
+ //#endregion
1074
+ //#region src/server/render-api.ts
1075
+ /** Shared no-plugin instance — created once, reused for all standalone calls. */
896
1076
  var _defaultSSR = createSSR();
1077
+ /**
1078
+ * Render JSX element or App to a ReadableStream.
1079
+ *
1080
+ * Internally delegates to `createSSR().renderStream()`.
1081
+ *
1082
+ * @example
1083
+ * ```tsx
1084
+ * // Simple usage with JSX
1085
+ * renderToStream(<App />)
1086
+ *
1087
+ * // With App instance for DI/plugins
1088
+ * const app = defineApp(<App />).use(router);
1089
+ * renderToStream(app)
1090
+ * ```
1091
+ */
897
1092
  function renderToStream(input, context) {
898
1093
  return _defaultSSR.renderStream(input, context);
899
1094
  }
1095
+ /**
1096
+ * Render JSX element or App to a Node.js Readable stream.
1097
+ *
1098
+ * Faster than `renderToStream()` on Node.js because it bypasses WebStream
1099
+ * overhead entirely. Recommended for Express, Fastify, H3, and other
1100
+ * Node.js HTTP frameworks.
1101
+ *
1102
+ * @example
1103
+ * ```tsx
1104
+ * import { renderToNodeStream } from '@sigx/server-renderer/server';
1105
+ *
1106
+ * const stream = renderToNodeStream(<App />);
1107
+ * stream.pipe(res);
1108
+ * ```
1109
+ */
900
1110
  function renderToNodeStream(input, context) {
901
1111
  return _defaultSSR.renderNodeStream(input, context);
902
1112
  }
1113
+ /**
1114
+ * Render with callbacks for fine-grained streaming control.
1115
+ *
1116
+ * Internally delegates to `createSSR().renderStreamWithCallbacks()`.
1117
+ *
1118
+ * @example
1119
+ * ```tsx
1120
+ * const app = defineApp(<App />).use(router);
1121
+ * await renderToStreamWithCallbacks(app, callbacks)
1122
+ * ```
1123
+ */
903
1124
  async function renderToStreamWithCallbacks(input, callbacks, context) {
904
1125
  return _defaultSSR.renderStreamWithCallbacks(input, callbacks, context);
905
1126
  }
1127
+ /**
1128
+ * Render JSX element or App to string.
1129
+ *
1130
+ * Internally delegates to `createSSR().render()`.
1131
+ *
1132
+ * @example
1133
+ * ```tsx
1134
+ * const html = await renderToString(<App />);
1135
+ *
1136
+ * const app = defineApp(<App />).use(router);
1137
+ * const html = await renderToString(app);
1138
+ * ```
1139
+ */
906
1140
  async function renderToString(input, context) {
907
1141
  return _defaultSSR.render(input, context);
908
1142
  }
1143
+ //#endregion
1144
+ //#region src/server/index.ts
909
1145
  initDirectivesForSSR();
1146
+ //#endregion
910
1147
  export { createSSR as a, renderHeadToString as c, generateReplacementScript as d, generateStreamingScript as f, initDirectivesForSSR as h, renderToString as i, useHead as l, createSSRContext as m, renderToStream as n, collectSSRHead as o, renderVNodeToString as p, renderToStreamWithCallbacks as r, enableSSRHead as s, renderToNodeStream as t, escapeJsonForScript as u };
911
1148
 
912
- //# sourceMappingURL=server-pSrHP504.js.map
1149
+ //# sourceMappingURL=server-Di7tiBy1.js.map