@sigx/server-renderer 0.1.25 → 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.
- package/dist/client/index.js +1 -1
- package/dist/{client-DVLUaAq6.js → client-BnGv1-Vu.js} +119 -3
- package/dist/{client-DVLUaAq6.js.map → client-BnGv1-Vu.js.map} +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/{server-pSrHP504.js → server-Di7tiBy1.js} +239 -2
- package/dist/{server-pSrHP504.js.map → server-Di7tiBy1.js.map} +1 -1
- package/dist/{types-CkI_lB93.js → types-DYlI_C8F.js} +17 -4
- package/dist/{types-CkI_lB93.js.map → types-DYlI_C8F.js.map} +1 -1
- package/package.json +4 -4
|
@@ -1,20 +1,50 @@
|
|
|
1
|
-
import { n as internals_exports, r as __commonJSMin } from "./types-
|
|
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
|
"<": "<",
|
|
@@ -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, "&").replace(/"/g, """);
|
|
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-
|
|
1149
|
+
//# sourceMappingURL=server-Di7tiBy1.js.map
|