@fictjs/ssr 0.20.0 → 0.21.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/README.md +13 -3
- package/dist/index.cjs +34 -24
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +14 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -250,11 +250,17 @@ interface RenderToStringOptions {
|
|
|
250
250
|
snapshotTarget?: 'container' | 'body' | 'head'
|
|
251
251
|
|
|
252
252
|
// Runtime Configuration
|
|
253
|
-
exposeGlobals?: boolean // Default:
|
|
253
|
+
exposeGlobals?: boolean // Default: false; opt-in compatibility mode
|
|
254
254
|
manifest?: Record<string, string> | string
|
|
255
255
|
}
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
+
By default SSR does not write `window`, `document`, `Node`, or related DOM constructors to
|
|
259
|
+
`globalThis`. This keeps concurrent renders from racing over process-global DOM state. Components
|
|
260
|
+
should use Fict's render-provided document/ownerDocument paths; set `exposeGlobals: true` only for
|
|
261
|
+
legacy code that still reads DOM globals during server render. That compatibility mode is restored
|
|
262
|
+
on `dispose()`, but it is not concurrency-safe while overlapping renders are active.
|
|
263
|
+
|
|
258
264
|
### renderToDocument
|
|
259
265
|
|
|
260
266
|
```typescript
|
|
@@ -306,6 +312,10 @@ The asset is published as `@fictjs/ssr/fict-stream-runtime.js`. Build tools that
|
|
|
306
312
|
need to materialize it themselves can import `createStreamRuntimeCode` from
|
|
307
313
|
`@fictjs/ssr/stream-runtime`.
|
|
308
314
|
|
|
315
|
+
Trusted Types deployments should use this external observer runtime. The patch
|
|
316
|
+
runtime moves `<template>` content with DOM APIs (`content` + `insertBefore`) and
|
|
317
|
+
does not call `innerHTML`, `insertAdjacentHTML`, `eval`, or `Function`.
|
|
318
|
+
|
|
309
319
|
### renderToPipeableStream
|
|
310
320
|
|
|
311
321
|
Node.js-style stream variant (compatible with `pipe()`).
|
|
@@ -324,7 +334,7 @@ await allReady
|
|
|
324
334
|
### renderToPartial
|
|
325
335
|
|
|
326
336
|
Generate a complete shell HTML plus a deferred patch stream for Partial Prerendering workflows.
|
|
327
|
-
This is an advanced API and currently considered **Preview** in v1.0.
|
|
337
|
+
This is an advanced API and currently considered **Experimental Preview** in v1.0; do not treat the return shape as frozen.
|
|
328
338
|
|
|
329
339
|
```typescript
|
|
330
340
|
import { renderToPartial } from '@fictjs/ssr'
|
|
@@ -469,7 +479,7 @@ Generated detailed `fict.manifest.json` during production build, mapping virtual
|
|
|
469
479
|
|
|
470
480
|
## Partial Prerendering
|
|
471
481
|
|
|
472
|
-
`renderToPartial()` enables a PPR-style split:
|
|
482
|
+
`renderToPartial()` is an **Experimental Preview** API that enables a PPR-style split:
|
|
473
483
|
|
|
474
484
|
1. **Shell phase**: serve/cache `shell` as static-first HTML.
|
|
475
485
|
2. **Deferred phase**: deliver `stream` patches for resolved Suspense boundaries.
|
package/dist/index.cjs
CHANGED
|
@@ -30,10 +30,11 @@ __export(index_exports, {
|
|
|
30
30
|
});
|
|
31
31
|
module.exports = __toCommonJS(index_exports);
|
|
32
32
|
var import_runtime = require("@fictjs/runtime");
|
|
33
|
-
var
|
|
33
|
+
var import_internal2 = require("@fictjs/runtime/internal");
|
|
34
34
|
var import_linkedom = require("linkedom");
|
|
35
35
|
|
|
36
36
|
// src/globals.ts
|
|
37
|
+
var import_internal = require("@fictjs/runtime/internal");
|
|
37
38
|
function installGlobals(window, document) {
|
|
38
39
|
const win = window;
|
|
39
40
|
const required = {
|
|
@@ -83,6 +84,14 @@ function installManifest(manifest) {
|
|
|
83
84
|
} else {
|
|
84
85
|
resolved = manifest;
|
|
85
86
|
}
|
|
87
|
+
const session = (0, import_internal.__fictGetCurrentSSRSession)();
|
|
88
|
+
if (session) {
|
|
89
|
+
const previous = session.manifest;
|
|
90
|
+
session.manifest = resolved;
|
|
91
|
+
return () => {
|
|
92
|
+
session.manifest = previous;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
86
95
|
const key = "__FICT_MANIFEST__";
|
|
87
96
|
const snapshot = {
|
|
88
97
|
exists: Object.prototype.hasOwnProperty.call(globalThis, key),
|
|
@@ -431,12 +440,12 @@ function createSSRDocument(html = DEFAULT_HTML) {
|
|
|
431
440
|
return { window, document };
|
|
432
441
|
}
|
|
433
442
|
function renderToDocument(view, options = {}) {
|
|
434
|
-
const session = (0,
|
|
435
|
-
return (0,
|
|
443
|
+
const session = (0, import_internal2.__fictCreateSSRSession)();
|
|
444
|
+
return (0, import_internal2.__fictRunWithSSRSession)(session, () => renderToDocumentInSession(view, options));
|
|
436
445
|
}
|
|
437
446
|
function renderToDocumentInSession(view, options) {
|
|
438
447
|
const includeSnapshot = options.includeSnapshot !== false;
|
|
439
|
-
(0,
|
|
448
|
+
(0, import_internal2.__fictEnableSSR)();
|
|
440
449
|
let dom;
|
|
441
450
|
let restoreGlobals2 = () => {
|
|
442
451
|
};
|
|
@@ -448,23 +457,23 @@ function renderToDocumentInSession(view, options) {
|
|
|
448
457
|
try {
|
|
449
458
|
dom = resolveDom(options);
|
|
450
459
|
const { document, window } = dom;
|
|
451
|
-
const shouldExpose = options.exposeGlobals
|
|
460
|
+
const shouldExpose = options.exposeGlobals === true;
|
|
452
461
|
restoreGlobals2 = shouldExpose ? installGlobals(window, document) : () => {
|
|
453
462
|
};
|
|
454
463
|
restoreManifest = installManifest(options.manifest);
|
|
455
464
|
container = resolveContainer(document, options);
|
|
456
465
|
teardown = (0, import_runtime.render)(view, container);
|
|
457
466
|
if (includeSnapshot) {
|
|
458
|
-
const state = (0,
|
|
467
|
+
const state = (0, import_internal2.__fictSerializeSSRState)();
|
|
459
468
|
injectSnapshot(document, container, state, options);
|
|
460
469
|
}
|
|
461
470
|
} catch (error) {
|
|
462
|
-
(0,
|
|
471
|
+
(0, import_internal2.__fictDisableSSR)();
|
|
463
472
|
restoreGlobals2();
|
|
464
473
|
restoreManifest();
|
|
465
474
|
throw error;
|
|
466
475
|
}
|
|
467
|
-
(0,
|
|
476
|
+
(0, import_internal2.__fictDisableSSR)();
|
|
468
477
|
const html = serializeOutput(dom.document, container, options);
|
|
469
478
|
const dispose = () => {
|
|
470
479
|
try {
|
|
@@ -651,14 +660,14 @@ function isPromiseLike(value) {
|
|
|
651
660
|
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
652
661
|
}
|
|
653
662
|
function startStreamingRender(view, options, writer, control = {}) {
|
|
654
|
-
const session = (0,
|
|
655
|
-
return (0,
|
|
663
|
+
const session = (0, import_internal2.__fictCreateSSRSession)();
|
|
664
|
+
return (0, import_internal2.__fictRunWithSSRSession)(
|
|
656
665
|
session,
|
|
657
666
|
() => startStreamingRenderInSession(session, view, options, writer, control)
|
|
658
667
|
);
|
|
659
668
|
}
|
|
660
669
|
function startStreamingRenderInSession(session, view, options, writer, control = {}) {
|
|
661
|
-
const runInSession = (fn) => (0,
|
|
670
|
+
const runInSession = (fn) => (0, import_internal2.__fictRunWithSSRSession)(session, fn);
|
|
662
671
|
const resolvedOptions = {
|
|
663
672
|
...options,
|
|
664
673
|
// Streaming requires a real document; default to fullDocument when unspecified.
|
|
@@ -779,10 +788,10 @@ function startStreamingRenderInSession(session, view, options, writer, control =
|
|
|
779
788
|
const writeSnapshotForScopes = (scopeIds) => {
|
|
780
789
|
runInSession(() => {
|
|
781
790
|
if (!includeSnapshot || scopeIds.length === 0) return;
|
|
782
|
-
const registry = (0,
|
|
791
|
+
const registry = (0, import_internal2.__fictGetScopeRegistry)();
|
|
783
792
|
const pending = scopeIds.filter((id) => registry.has(id) && !sentScopes.has(id));
|
|
784
793
|
if (pending.length === 0) return;
|
|
785
|
-
const snapshot = (0,
|
|
794
|
+
const snapshot = (0, import_internal2.__fictSerializeSSRStateForScopes)(pending);
|
|
786
795
|
const ids = Object.keys(snapshot.scopes);
|
|
787
796
|
if (ids.length === 0) return;
|
|
788
797
|
const chunk = buildIncrementalSnapshotChunk(snapshot, resolvedOptions);
|
|
@@ -796,13 +805,13 @@ function startStreamingRenderInSession(session, view, options, writer, control =
|
|
|
796
805
|
};
|
|
797
806
|
const writeSnapshotForBoundary = (boundary) => {
|
|
798
807
|
runInSession(() => {
|
|
799
|
-
const scopes = (0,
|
|
808
|
+
const scopes = (0, import_internal2.__fictGetScopesForBoundary)(boundary);
|
|
800
809
|
writeSnapshotForScopes(scopes);
|
|
801
810
|
});
|
|
802
811
|
};
|
|
803
812
|
const writeRemainingSnapshots = () => {
|
|
804
813
|
runInSession(() => {
|
|
805
|
-
const scopes = Array.from((0,
|
|
814
|
+
const scopes = Array.from((0, import_internal2.__fictGetScopeRegistry)().keys());
|
|
806
815
|
writeSnapshotForScopes(scopes);
|
|
807
816
|
});
|
|
808
817
|
};
|
|
@@ -811,14 +820,15 @@ function startStreamingRenderInSession(session, view, options, writer, control =
|
|
|
811
820
|
removeAbortListener = () => {
|
|
812
821
|
};
|
|
813
822
|
runInSession(() => {
|
|
814
|
-
(0,
|
|
815
|
-
(0,
|
|
823
|
+
(0, import_internal2.__fictSetSSRStreamHooks)(null);
|
|
824
|
+
(0, import_internal2.__fictDisableSSR)();
|
|
816
825
|
});
|
|
817
|
-
restoreGlobals2();
|
|
818
|
-
restoreManifest();
|
|
819
826
|
try {
|
|
820
827
|
teardown();
|
|
821
828
|
} catch {
|
|
829
|
+
} finally {
|
|
830
|
+
restoreGlobals2();
|
|
831
|
+
restoreManifest();
|
|
822
832
|
}
|
|
823
833
|
};
|
|
824
834
|
const finalize = () => {
|
|
@@ -826,7 +836,7 @@ function startStreamingRenderInSession(session, view, options, writer, control =
|
|
|
826
836
|
closed = true;
|
|
827
837
|
if (mode === "all" && dom && container && !wroteShell) {
|
|
828
838
|
if (includeSnapshot) {
|
|
829
|
-
const snapshot = (0,
|
|
839
|
+
const snapshot = (0, import_internal2.__fictSerializeSSRState)();
|
|
830
840
|
injectSnapshot(dom.document, container, snapshot, resolvedOptions);
|
|
831
841
|
}
|
|
832
842
|
const fullHtml = serializeOutput(dom.document, container, resolvedOptions);
|
|
@@ -911,10 +921,10 @@ function startStreamingRenderInSession(session, view, options, writer, control =
|
|
|
911
921
|
}
|
|
912
922
|
}
|
|
913
923
|
try {
|
|
914
|
-
(0,
|
|
915
|
-
(0,
|
|
924
|
+
(0, import_internal2.__fictEnableSSR)();
|
|
925
|
+
(0, import_internal2.__fictSetSSRStreamHooks)(hooks);
|
|
916
926
|
dom = resolveDom(resolvedOptions);
|
|
917
|
-
restoreGlobals2 = resolvedOptions.exposeGlobals
|
|
927
|
+
restoreGlobals2 = resolvedOptions.exposeGlobals === true ? installGlobals(dom.window, dom.document) : () => {
|
|
918
928
|
};
|
|
919
929
|
restoreManifest = installManifest(resolvedOptions.manifest);
|
|
920
930
|
container = resolveContainer(dom.document, resolvedOptions);
|
|
@@ -944,7 +954,7 @@ function startStreamingRenderInSession(session, view, options, writer, control =
|
|
|
944
954
|
enqueueWrite(shellHtml + streamRuntime);
|
|
945
955
|
}
|
|
946
956
|
wroteShell = true;
|
|
947
|
-
writeSnapshotForScopes(Array.from((0,
|
|
957
|
+
writeSnapshotForScopes(Array.from((0, import_internal2.__fictGetScopeRegistry)().keys()));
|
|
948
958
|
if (shellCarriesTail && tailHtml) {
|
|
949
959
|
enqueueWrite(tailHtml);
|
|
950
960
|
tailHtml = "";
|
package/dist/index.d.cts
CHANGED
|
@@ -53,7 +53,8 @@ interface RenderToStringOptions {
|
|
|
53
53
|
doctype?: string | null;
|
|
54
54
|
/**
|
|
55
55
|
* Expose DOM globals (window/document/Node/Element/etc) during render.
|
|
56
|
-
* Defaults to true
|
|
56
|
+
* Defaults to false. Set to true only for compatibility with components
|
|
57
|
+
* that still read process-global DOM objects during server rendering.
|
|
57
58
|
*/
|
|
58
59
|
exposeGlobals?: boolean;
|
|
59
60
|
/**
|
|
@@ -128,6 +129,9 @@ interface PipeableStream {
|
|
|
128
129
|
interface PartialPrerenderResult {
|
|
129
130
|
/**
|
|
130
131
|
* Complete shell HTML (fallbacks + markers + initial snapshot scripts).
|
|
132
|
+
*
|
|
133
|
+
* @experimental Preview API for v1.0; the access pattern may change before
|
|
134
|
+
* this becomes stable.
|
|
131
135
|
*/
|
|
132
136
|
shell: string;
|
|
133
137
|
/**
|
|
@@ -149,6 +153,10 @@ declare function renderToString(view: () => FictNode, options?: RenderToStringOp
|
|
|
149
153
|
declare function renderToStringAsync(view: () => FictNode, options?: RenderToStringOptions): Promise<string>;
|
|
150
154
|
declare function renderToStream(view: () => FictNode, options?: RenderToStreamOptions): ReadableStream<Uint8Array>;
|
|
151
155
|
declare function renderToPipeableStream(view: () => FictNode, options?: RenderToStreamOptions): PipeableStream;
|
|
156
|
+
/**
|
|
157
|
+
* @experimental Preview API for v1.0; the return shape may change before this
|
|
158
|
+
* becomes stable.
|
|
159
|
+
*/
|
|
152
160
|
declare function renderToPartial(view: () => FictNode, options?: RenderToStreamOptions): PartialPrerenderResult;
|
|
153
161
|
|
|
154
162
|
export { type PartialPrerenderResult, type PipeableStream, type RenderToDocumentResult, type RenderToStreamOptions, type RenderToStringOptions, type SSRDom, createSSRDocument, renderToDocument, renderToPartial, renderToPipeableStream, renderToStream, renderToString, renderToStringAsync };
|
package/dist/index.d.ts
CHANGED
|
@@ -53,7 +53,8 @@ interface RenderToStringOptions {
|
|
|
53
53
|
doctype?: string | null;
|
|
54
54
|
/**
|
|
55
55
|
* Expose DOM globals (window/document/Node/Element/etc) during render.
|
|
56
|
-
* Defaults to true
|
|
56
|
+
* Defaults to false. Set to true only for compatibility with components
|
|
57
|
+
* that still read process-global DOM objects during server rendering.
|
|
57
58
|
*/
|
|
58
59
|
exposeGlobals?: boolean;
|
|
59
60
|
/**
|
|
@@ -128,6 +129,9 @@ interface PipeableStream {
|
|
|
128
129
|
interface PartialPrerenderResult {
|
|
129
130
|
/**
|
|
130
131
|
* Complete shell HTML (fallbacks + markers + initial snapshot scripts).
|
|
132
|
+
*
|
|
133
|
+
* @experimental Preview API for v1.0; the access pattern may change before
|
|
134
|
+
* this becomes stable.
|
|
131
135
|
*/
|
|
132
136
|
shell: string;
|
|
133
137
|
/**
|
|
@@ -149,6 +153,10 @@ declare function renderToString(view: () => FictNode, options?: RenderToStringOp
|
|
|
149
153
|
declare function renderToStringAsync(view: () => FictNode, options?: RenderToStringOptions): Promise<string>;
|
|
150
154
|
declare function renderToStream(view: () => FictNode, options?: RenderToStreamOptions): ReadableStream<Uint8Array>;
|
|
151
155
|
declare function renderToPipeableStream(view: () => FictNode, options?: RenderToStreamOptions): PipeableStream;
|
|
156
|
+
/**
|
|
157
|
+
* @experimental Preview API for v1.0; the return shape may change before this
|
|
158
|
+
* becomes stable.
|
|
159
|
+
*/
|
|
152
160
|
declare function renderToPartial(view: () => FictNode, options?: RenderToStreamOptions): PartialPrerenderResult;
|
|
153
161
|
|
|
154
162
|
export { type PartialPrerenderResult, type PipeableStream, type RenderToDocumentResult, type RenderToStreamOptions, type RenderToStringOptions, type SSRDom, createSSRDocument, renderToDocument, renderToPartial, renderToPipeableStream, renderToStream, renderToString, renderToStringAsync };
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { parseHTML } from "linkedom";
|
|
19
19
|
|
|
20
20
|
// src/globals.ts
|
|
21
|
+
import { __fictGetCurrentSSRSession } from "@fictjs/runtime/internal";
|
|
21
22
|
function installGlobals(window, document) {
|
|
22
23
|
const win = window;
|
|
23
24
|
const required = {
|
|
@@ -67,6 +68,14 @@ function installManifest(manifest) {
|
|
|
67
68
|
} else {
|
|
68
69
|
resolved = manifest;
|
|
69
70
|
}
|
|
71
|
+
const session = __fictGetCurrentSSRSession();
|
|
72
|
+
if (session) {
|
|
73
|
+
const previous = session.manifest;
|
|
74
|
+
session.manifest = resolved;
|
|
75
|
+
return () => {
|
|
76
|
+
session.manifest = previous;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
70
79
|
const key = "__FICT_MANIFEST__";
|
|
71
80
|
const snapshot = {
|
|
72
81
|
exists: Object.prototype.hasOwnProperty.call(globalThis, key),
|
|
@@ -425,7 +434,7 @@ function renderToDocumentInSession(view, options) {
|
|
|
425
434
|
try {
|
|
426
435
|
dom = resolveDom(options);
|
|
427
436
|
const { document, window } = dom;
|
|
428
|
-
const shouldExpose = options.exposeGlobals
|
|
437
|
+
const shouldExpose = options.exposeGlobals === true;
|
|
429
438
|
restoreGlobals2 = shouldExpose ? installGlobals(window, document) : () => {
|
|
430
439
|
};
|
|
431
440
|
restoreManifest = installManifest(options.manifest);
|
|
@@ -791,11 +800,12 @@ function startStreamingRenderInSession(session, view, options, writer, control =
|
|
|
791
800
|
__fictSetSSRStreamHooks(null);
|
|
792
801
|
__fictDisableSSR();
|
|
793
802
|
});
|
|
794
|
-
restoreGlobals2();
|
|
795
|
-
restoreManifest();
|
|
796
803
|
try {
|
|
797
804
|
teardown();
|
|
798
805
|
} catch {
|
|
806
|
+
} finally {
|
|
807
|
+
restoreGlobals2();
|
|
808
|
+
restoreManifest();
|
|
799
809
|
}
|
|
800
810
|
};
|
|
801
811
|
const finalize = () => {
|
|
@@ -891,7 +901,7 @@ function startStreamingRenderInSession(session, view, options, writer, control =
|
|
|
891
901
|
__fictEnableSSR();
|
|
892
902
|
__fictSetSSRStreamHooks(hooks);
|
|
893
903
|
dom = resolveDom(resolvedOptions);
|
|
894
|
-
restoreGlobals2 = resolvedOptions.exposeGlobals
|
|
904
|
+
restoreGlobals2 = resolvedOptions.exposeGlobals === true ? installGlobals(dom.window, dom.document) : () => {
|
|
895
905
|
};
|
|
896
906
|
restoreManifest = installManifest(resolvedOptions.manifest);
|
|
897
907
|
container = resolveContainer(dom.document, resolvedOptions);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fictjs/ssr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Fict server-side rendering",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"linkedom": "^0.18.12",
|
|
36
|
-
"@fictjs/runtime": "0.
|
|
36
|
+
"@fictjs/runtime": "0.21.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"tsup": "^8.5.1",
|
|
40
|
-
"fict": "0.
|
|
40
|
+
"fict": "0.21.0"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"fict",
|