@fictjs/ssr 0.21.0 → 0.22.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 +2 -1
- package/dist/chunk-LW7ALN63.js +1129 -0
- package/dist/experimental.cjs +780 -0
- package/dist/experimental.d.cts +164 -0
- package/dist/experimental.d.ts +164 -0
- package/dist/experimental.js +7 -0
- package/dist/index.cjs +42 -141
- package/dist/index.d.cts +2 -162
- package/dist/index.d.ts +2 -162
- package/dist/index.js +8 -1092
- package/package.json +11 -5
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { FictNode } from '@fictjs/runtime';
|
|
2
|
+
|
|
3
|
+
interface SSRDom {
|
|
4
|
+
window: Window;
|
|
5
|
+
document: Document;
|
|
6
|
+
}
|
|
7
|
+
interface RenderToStringOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Provide a pre-created DOM (document + window). If omitted, a new DOM is
|
|
10
|
+
* created per render using `html`.
|
|
11
|
+
*/
|
|
12
|
+
dom?: SSRDom;
|
|
13
|
+
/**
|
|
14
|
+
* Provide a document directly. If `window` is omitted, `document.defaultView`
|
|
15
|
+
* will be used when available.
|
|
16
|
+
*/
|
|
17
|
+
document?: Document;
|
|
18
|
+
/**
|
|
19
|
+
* Provide a window directly. If `document` is omitted, `window.document` is used.
|
|
20
|
+
*/
|
|
21
|
+
window?: Window;
|
|
22
|
+
/**
|
|
23
|
+
* HTML template used when creating a new DOM.
|
|
24
|
+
*/
|
|
25
|
+
html?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Provide a container element to render into.
|
|
28
|
+
*/
|
|
29
|
+
container?: HTMLElement;
|
|
30
|
+
/**
|
|
31
|
+
* Tag name for the auto-created container.
|
|
32
|
+
*/
|
|
33
|
+
containerTag?: string;
|
|
34
|
+
/**
|
|
35
|
+
* id applied to the auto-created container.
|
|
36
|
+
*/
|
|
37
|
+
containerId?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Additional attributes applied to the auto-created container.
|
|
40
|
+
*/
|
|
41
|
+
containerAttributes?: Record<string, string | number | boolean | null | undefined>;
|
|
42
|
+
/**
|
|
43
|
+
* Return the container element including its outer tag.
|
|
44
|
+
*/
|
|
45
|
+
includeContainer?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Return a full HTML document string (doctype + documentElement.outerHTML).
|
|
48
|
+
*/
|
|
49
|
+
fullDocument?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Override doctype when `fullDocument` is true. Use `null` to omit.
|
|
52
|
+
*/
|
|
53
|
+
doctype?: string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Expose DOM globals (window/document/Node/Element/etc) during render.
|
|
56
|
+
* Defaults to false. Set to true only for compatibility with components
|
|
57
|
+
* that still read process-global DOM objects during server rendering.
|
|
58
|
+
*/
|
|
59
|
+
exposeGlobals?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Manifest mapping module URLs to built client chunk URLs.
|
|
62
|
+
* Can be an object or a path to a JSON file.
|
|
63
|
+
* File path mode requires Deno sync filesystem access or a CommonJS
|
|
64
|
+
* environment where `require('node:fs')` is available. Pass an object when
|
|
65
|
+
* rendering from Node ESM or edge runtimes.
|
|
66
|
+
*/
|
|
67
|
+
manifest?: Record<string, string> | string;
|
|
68
|
+
/**
|
|
69
|
+
* Include the SSR snapshot script for resumability.
|
|
70
|
+
* Defaults to true.
|
|
71
|
+
*/
|
|
72
|
+
includeSnapshot?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Script element id for the snapshot.
|
|
75
|
+
*/
|
|
76
|
+
snapshotScriptId?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Where to append the snapshot script when not returning full document.
|
|
79
|
+
* Defaults to 'container'.
|
|
80
|
+
*/
|
|
81
|
+
snapshotTarget?: 'container' | 'body' | 'head';
|
|
82
|
+
/**
|
|
83
|
+
* Nonce applied to generated <script> tags for CSP compatibility.
|
|
84
|
+
*/
|
|
85
|
+
scriptNonce?: string;
|
|
86
|
+
}
|
|
87
|
+
interface RenderToStreamOptions extends RenderToStringOptions {
|
|
88
|
+
/**
|
|
89
|
+
* Streaming mode:
|
|
90
|
+
* - 'shell': send fallback shell first, then patch resolved boundaries
|
|
91
|
+
* - 'all': wait for all suspense boundaries, then send full HTML
|
|
92
|
+
*/
|
|
93
|
+
mode?: 'shell' | 'all';
|
|
94
|
+
/**
|
|
95
|
+
* Called once the initial shell has been written.
|
|
96
|
+
*/
|
|
97
|
+
onShellReady?: () => void;
|
|
98
|
+
/**
|
|
99
|
+
* Called once all pending boundaries resolve and the stream completes.
|
|
100
|
+
*/
|
|
101
|
+
onAllReady?: () => void;
|
|
102
|
+
/**
|
|
103
|
+
* Called when an error occurs during streaming.
|
|
104
|
+
*/
|
|
105
|
+
onError?: (err: unknown) => void;
|
|
106
|
+
/**
|
|
107
|
+
* Abort signal to cancel the stream.
|
|
108
|
+
*/
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
/**
|
|
111
|
+
* How to load the streaming patch runtime.
|
|
112
|
+
* Defaults to 'inline'. Use 'external' with streamRuntimeSrc for strict CSP.
|
|
113
|
+
*/
|
|
114
|
+
streamRuntime?: 'inline' | 'external';
|
|
115
|
+
/**
|
|
116
|
+
* External streaming patch runtime URL when streamRuntime is 'external'.
|
|
117
|
+
*/
|
|
118
|
+
streamRuntimeSrc?: string;
|
|
119
|
+
/**
|
|
120
|
+
* How resolved Suspense patch chunks are applied.
|
|
121
|
+
* Defaults to 'inline' for inline runtimes and 'observer' for external runtimes.
|
|
122
|
+
*/
|
|
123
|
+
streamPatchMode?: 'inline' | 'observer';
|
|
124
|
+
}
|
|
125
|
+
interface PipeableStream {
|
|
126
|
+
pipe: (writable: NodeJS.WritableStream) => void;
|
|
127
|
+
abort: (reason?: unknown) => void;
|
|
128
|
+
shellReady: Promise<void>;
|
|
129
|
+
allReady: Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
interface PartialPrerenderResult {
|
|
132
|
+
/**
|
|
133
|
+
* Complete shell HTML (fallbacks + markers + initial snapshot scripts).
|
|
134
|
+
*
|
|
135
|
+
* @experimental Preview API for v1.0; the access pattern may change before
|
|
136
|
+
* this becomes stable.
|
|
137
|
+
*/
|
|
138
|
+
shell: string;
|
|
139
|
+
/**
|
|
140
|
+
* Stream of deferred patch chunks and incremental snapshots.
|
|
141
|
+
*/
|
|
142
|
+
stream: ReadableStream<Uint8Array>;
|
|
143
|
+
shellReady: Promise<void>;
|
|
144
|
+
allReady: Promise<void>;
|
|
145
|
+
abort: (reason?: unknown) => void;
|
|
146
|
+
}
|
|
147
|
+
interface RenderToDocumentResult extends SSRDom {
|
|
148
|
+
html: string;
|
|
149
|
+
container: HTMLElement;
|
|
150
|
+
dispose: () => void;
|
|
151
|
+
}
|
|
152
|
+
declare function createSSRDocument(html?: string): SSRDom;
|
|
153
|
+
declare function renderToDocument(view: () => FictNode, options?: RenderToStringOptions): RenderToDocumentResult;
|
|
154
|
+
declare function renderToString(view: () => FictNode, options?: RenderToStringOptions): string;
|
|
155
|
+
declare function renderToStringAsync(view: () => FictNode, options?: RenderToStringOptions): Promise<string>;
|
|
156
|
+
declare function renderToStream(view: () => FictNode, options?: RenderToStreamOptions): ReadableStream<Uint8Array>;
|
|
157
|
+
declare function renderToPipeableStream(view: () => FictNode, options?: RenderToStreamOptions): PipeableStream;
|
|
158
|
+
/**
|
|
159
|
+
* @experimental Preview API for v1.0; the return shape may change before this
|
|
160
|
+
* becomes stable.
|
|
161
|
+
*/
|
|
162
|
+
declare function renderToPartial(view: () => FictNode, options?: RenderToStreamOptions): PartialPrerenderResult;
|
|
163
|
+
|
|
164
|
+
export { type PipeableStream as P, type PartialPrerenderResult, type RenderToStringOptions as R, type SSRDom as S, renderToString as a, renderToStringAsync as b, createSSRDocument as c, renderToStream as d, renderToPipeableStream as e, type RenderToStreamOptions as f, type RenderToDocumentResult as g, renderToDocument as r, renderToPartial };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { FictNode } from '@fictjs/runtime';
|
|
2
|
+
|
|
3
|
+
interface SSRDom {
|
|
4
|
+
window: Window;
|
|
5
|
+
document: Document;
|
|
6
|
+
}
|
|
7
|
+
interface RenderToStringOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Provide a pre-created DOM (document + window). If omitted, a new DOM is
|
|
10
|
+
* created per render using `html`.
|
|
11
|
+
*/
|
|
12
|
+
dom?: SSRDom;
|
|
13
|
+
/**
|
|
14
|
+
* Provide a document directly. If `window` is omitted, `document.defaultView`
|
|
15
|
+
* will be used when available.
|
|
16
|
+
*/
|
|
17
|
+
document?: Document;
|
|
18
|
+
/**
|
|
19
|
+
* Provide a window directly. If `document` is omitted, `window.document` is used.
|
|
20
|
+
*/
|
|
21
|
+
window?: Window;
|
|
22
|
+
/**
|
|
23
|
+
* HTML template used when creating a new DOM.
|
|
24
|
+
*/
|
|
25
|
+
html?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Provide a container element to render into.
|
|
28
|
+
*/
|
|
29
|
+
container?: HTMLElement;
|
|
30
|
+
/**
|
|
31
|
+
* Tag name for the auto-created container.
|
|
32
|
+
*/
|
|
33
|
+
containerTag?: string;
|
|
34
|
+
/**
|
|
35
|
+
* id applied to the auto-created container.
|
|
36
|
+
*/
|
|
37
|
+
containerId?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Additional attributes applied to the auto-created container.
|
|
40
|
+
*/
|
|
41
|
+
containerAttributes?: Record<string, string | number | boolean | null | undefined>;
|
|
42
|
+
/**
|
|
43
|
+
* Return the container element including its outer tag.
|
|
44
|
+
*/
|
|
45
|
+
includeContainer?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Return a full HTML document string (doctype + documentElement.outerHTML).
|
|
48
|
+
*/
|
|
49
|
+
fullDocument?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Override doctype when `fullDocument` is true. Use `null` to omit.
|
|
52
|
+
*/
|
|
53
|
+
doctype?: string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Expose DOM globals (window/document/Node/Element/etc) during render.
|
|
56
|
+
* Defaults to false. Set to true only for compatibility with components
|
|
57
|
+
* that still read process-global DOM objects during server rendering.
|
|
58
|
+
*/
|
|
59
|
+
exposeGlobals?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Manifest mapping module URLs to built client chunk URLs.
|
|
62
|
+
* Can be an object or a path to a JSON file.
|
|
63
|
+
* File path mode requires Deno sync filesystem access or a CommonJS
|
|
64
|
+
* environment where `require('node:fs')` is available. Pass an object when
|
|
65
|
+
* rendering from Node ESM or edge runtimes.
|
|
66
|
+
*/
|
|
67
|
+
manifest?: Record<string, string> | string;
|
|
68
|
+
/**
|
|
69
|
+
* Include the SSR snapshot script for resumability.
|
|
70
|
+
* Defaults to true.
|
|
71
|
+
*/
|
|
72
|
+
includeSnapshot?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Script element id for the snapshot.
|
|
75
|
+
*/
|
|
76
|
+
snapshotScriptId?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Where to append the snapshot script when not returning full document.
|
|
79
|
+
* Defaults to 'container'.
|
|
80
|
+
*/
|
|
81
|
+
snapshotTarget?: 'container' | 'body' | 'head';
|
|
82
|
+
/**
|
|
83
|
+
* Nonce applied to generated <script> tags for CSP compatibility.
|
|
84
|
+
*/
|
|
85
|
+
scriptNonce?: string;
|
|
86
|
+
}
|
|
87
|
+
interface RenderToStreamOptions extends RenderToStringOptions {
|
|
88
|
+
/**
|
|
89
|
+
* Streaming mode:
|
|
90
|
+
* - 'shell': send fallback shell first, then patch resolved boundaries
|
|
91
|
+
* - 'all': wait for all suspense boundaries, then send full HTML
|
|
92
|
+
*/
|
|
93
|
+
mode?: 'shell' | 'all';
|
|
94
|
+
/**
|
|
95
|
+
* Called once the initial shell has been written.
|
|
96
|
+
*/
|
|
97
|
+
onShellReady?: () => void;
|
|
98
|
+
/**
|
|
99
|
+
* Called once all pending boundaries resolve and the stream completes.
|
|
100
|
+
*/
|
|
101
|
+
onAllReady?: () => void;
|
|
102
|
+
/**
|
|
103
|
+
* Called when an error occurs during streaming.
|
|
104
|
+
*/
|
|
105
|
+
onError?: (err: unknown) => void;
|
|
106
|
+
/**
|
|
107
|
+
* Abort signal to cancel the stream.
|
|
108
|
+
*/
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
/**
|
|
111
|
+
* How to load the streaming patch runtime.
|
|
112
|
+
* Defaults to 'inline'. Use 'external' with streamRuntimeSrc for strict CSP.
|
|
113
|
+
*/
|
|
114
|
+
streamRuntime?: 'inline' | 'external';
|
|
115
|
+
/**
|
|
116
|
+
* External streaming patch runtime URL when streamRuntime is 'external'.
|
|
117
|
+
*/
|
|
118
|
+
streamRuntimeSrc?: string;
|
|
119
|
+
/**
|
|
120
|
+
* How resolved Suspense patch chunks are applied.
|
|
121
|
+
* Defaults to 'inline' for inline runtimes and 'observer' for external runtimes.
|
|
122
|
+
*/
|
|
123
|
+
streamPatchMode?: 'inline' | 'observer';
|
|
124
|
+
}
|
|
125
|
+
interface PipeableStream {
|
|
126
|
+
pipe: (writable: NodeJS.WritableStream) => void;
|
|
127
|
+
abort: (reason?: unknown) => void;
|
|
128
|
+
shellReady: Promise<void>;
|
|
129
|
+
allReady: Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
interface PartialPrerenderResult {
|
|
132
|
+
/**
|
|
133
|
+
* Complete shell HTML (fallbacks + markers + initial snapshot scripts).
|
|
134
|
+
*
|
|
135
|
+
* @experimental Preview API for v1.0; the access pattern may change before
|
|
136
|
+
* this becomes stable.
|
|
137
|
+
*/
|
|
138
|
+
shell: string;
|
|
139
|
+
/**
|
|
140
|
+
* Stream of deferred patch chunks and incremental snapshots.
|
|
141
|
+
*/
|
|
142
|
+
stream: ReadableStream<Uint8Array>;
|
|
143
|
+
shellReady: Promise<void>;
|
|
144
|
+
allReady: Promise<void>;
|
|
145
|
+
abort: (reason?: unknown) => void;
|
|
146
|
+
}
|
|
147
|
+
interface RenderToDocumentResult extends SSRDom {
|
|
148
|
+
html: string;
|
|
149
|
+
container: HTMLElement;
|
|
150
|
+
dispose: () => void;
|
|
151
|
+
}
|
|
152
|
+
declare function createSSRDocument(html?: string): SSRDom;
|
|
153
|
+
declare function renderToDocument(view: () => FictNode, options?: RenderToStringOptions): RenderToDocumentResult;
|
|
154
|
+
declare function renderToString(view: () => FictNode, options?: RenderToStringOptions): string;
|
|
155
|
+
declare function renderToStringAsync(view: () => FictNode, options?: RenderToStringOptions): Promise<string>;
|
|
156
|
+
declare function renderToStream(view: () => FictNode, options?: RenderToStreamOptions): ReadableStream<Uint8Array>;
|
|
157
|
+
declare function renderToPipeableStream(view: () => FictNode, options?: RenderToStreamOptions): PipeableStream;
|
|
158
|
+
/**
|
|
159
|
+
* @experimental Preview API for v1.0; the return shape may change before this
|
|
160
|
+
* becomes stable.
|
|
161
|
+
*/
|
|
162
|
+
declare function renderToPartial(view: () => FictNode, options?: RenderToStreamOptions): PartialPrerenderResult;
|
|
163
|
+
|
|
164
|
+
export { type PipeableStream as P, type PartialPrerenderResult, type RenderToStringOptions as R, type SSRDom as S, renderToString as a, renderToStringAsync as b, createSSRDocument as c, renderToStream as d, renderToPipeableStream as e, type RenderToStreamOptions as f, type RenderToDocumentResult as g, renderToDocument as r, renderToPartial };
|
package/dist/index.cjs
CHANGED
|
@@ -22,13 +22,14 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
createSSRDocument: () => createSSRDocument,
|
|
24
24
|
renderToDocument: () => renderToDocument,
|
|
25
|
-
renderToPartial: () => renderToPartial,
|
|
26
25
|
renderToPipeableStream: () => renderToPipeableStream,
|
|
27
26
|
renderToStream: () => renderToStream,
|
|
28
27
|
renderToString: () => renderToString,
|
|
29
28
|
renderToStringAsync: () => renderToStringAsync
|
|
30
29
|
});
|
|
31
30
|
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/render-core.ts
|
|
32
33
|
var import_runtime = require("@fictjs/runtime");
|
|
33
34
|
var import_internal2 = require("@fictjs/runtime/internal");
|
|
34
35
|
var import_linkedom = require("linkedom");
|
|
@@ -138,7 +139,7 @@ function readTextFileFromPath(path) {
|
|
|
138
139
|
return fs.readFileSync(path, "utf8");
|
|
139
140
|
}
|
|
140
141
|
throw new Error(
|
|
141
|
-
"[fict/ssr] `manifest` as file path is only supported
|
|
142
|
+
"[fict/ssr] `manifest` as file path is only supported when Deno.readTextFileSync or CommonJS require is available. Pass a manifest object in Node ESM or edge runtimes."
|
|
142
143
|
);
|
|
143
144
|
}
|
|
144
145
|
function getNodeRequire() {
|
|
@@ -155,84 +156,6 @@ function getNodeRequire() {
|
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
// src/stream-bridge.ts
|
|
158
|
-
function createQueuedTextStream(options = {}) {
|
|
159
|
-
const encoder = new TextEncoder();
|
|
160
|
-
const queue = [];
|
|
161
|
-
let controller = null;
|
|
162
|
-
let closed = false;
|
|
163
|
-
let aborted;
|
|
164
|
-
const readyResolvers = [];
|
|
165
|
-
const resolveReady = () => {
|
|
166
|
-
if (!controller || (controller.desiredSize ?? 1) <= 0) return;
|
|
167
|
-
while (readyResolvers.length > 0) {
|
|
168
|
-
readyResolvers.shift()?.();
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
const drainReady = () => {
|
|
172
|
-
while (readyResolvers.length > 0) {
|
|
173
|
-
readyResolvers.shift()?.();
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
const abortQueue = (reason, notifyController = true) => {
|
|
177
|
-
if (closed || aborted !== void 0) return;
|
|
178
|
-
aborted = reason ?? new Error("Stream aborted");
|
|
179
|
-
queue.length = 0;
|
|
180
|
-
drainReady();
|
|
181
|
-
if (notifyController) {
|
|
182
|
-
controller?.error(aborted);
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
const stream = new ReadableStream({
|
|
186
|
-
start(ctrl) {
|
|
187
|
-
controller = ctrl;
|
|
188
|
-
for (const chunk of queue) {
|
|
189
|
-
ctrl.enqueue(chunk);
|
|
190
|
-
}
|
|
191
|
-
queue.length = 0;
|
|
192
|
-
if (aborted !== void 0) {
|
|
193
|
-
ctrl.error(aborted);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (closed) {
|
|
197
|
-
ctrl.close();
|
|
198
|
-
}
|
|
199
|
-
},
|
|
200
|
-
pull() {
|
|
201
|
-
resolveReady();
|
|
202
|
-
},
|
|
203
|
-
cancel(reason) {
|
|
204
|
-
abortQueue(reason, false);
|
|
205
|
-
options.onCancel?.(reason);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
const writer = {
|
|
209
|
-
write(chunk) {
|
|
210
|
-
if (closed || aborted !== void 0) return;
|
|
211
|
-
const data = encoder.encode(chunk);
|
|
212
|
-
if (controller) {
|
|
213
|
-
controller.enqueue(data);
|
|
214
|
-
if ((controller.desiredSize ?? 1) <= 0) {
|
|
215
|
-
return new Promise((resolve) => {
|
|
216
|
-
readyResolvers.push(resolve);
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
} else {
|
|
220
|
-
queue.push(data);
|
|
221
|
-
}
|
|
222
|
-
return void 0;
|
|
223
|
-
},
|
|
224
|
-
close() {
|
|
225
|
-
if (closed || aborted !== void 0) return;
|
|
226
|
-
closed = true;
|
|
227
|
-
drainReady();
|
|
228
|
-
controller?.close();
|
|
229
|
-
},
|
|
230
|
-
abort(reason) {
|
|
231
|
-
abortQueue(reason);
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
return { stream, writer };
|
|
235
|
-
}
|
|
236
159
|
function createPipeBridge() {
|
|
237
160
|
const nodeBridge = createNodePipeBridge();
|
|
238
161
|
if (nodeBridge) return nodeBridge;
|
|
@@ -240,6 +163,7 @@ function createPipeBridge() {
|
|
|
240
163
|
const buffer = [];
|
|
241
164
|
let state = "open";
|
|
242
165
|
let abortReason = null;
|
|
166
|
+
let sinkErrorHandler;
|
|
243
167
|
const safeWrite = (target, chunk) => {
|
|
244
168
|
try {
|
|
245
169
|
const ready = target.write(chunk);
|
|
@@ -253,7 +177,8 @@ function createPipeBridge() {
|
|
|
253
177
|
}
|
|
254
178
|
});
|
|
255
179
|
}
|
|
256
|
-
} catch {
|
|
180
|
+
} catch (error) {
|
|
181
|
+
sinkErrorHandler?.(error);
|
|
257
182
|
}
|
|
258
183
|
return void 0;
|
|
259
184
|
};
|
|
@@ -275,8 +200,14 @@ function createPipeBridge() {
|
|
|
275
200
|
safeEnd(target);
|
|
276
201
|
};
|
|
277
202
|
return {
|
|
278
|
-
pipe(writable) {
|
|
203
|
+
pipe(writable, options) {
|
|
279
204
|
targets.add(writable);
|
|
205
|
+
if (options?.onError) {
|
|
206
|
+
sinkErrorHandler = options.onError;
|
|
207
|
+
if (typeof writable.on === "function") {
|
|
208
|
+
writable.on("error", options.onError);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
280
211
|
if (buffer.length > 0) {
|
|
281
212
|
for (const chunk of buffer) {
|
|
282
213
|
safeWrite(writable, chunk);
|
|
@@ -334,14 +265,24 @@ function createNodePipeBridge() {
|
|
|
334
265
|
let piped = false;
|
|
335
266
|
let state = "open";
|
|
336
267
|
let abortReason = null;
|
|
268
|
+
const pendingDrains = /* @__PURE__ */ new Set();
|
|
269
|
+
const flushDrains = () => {
|
|
270
|
+
for (const resolve of pendingDrains) resolve();
|
|
271
|
+
pendingDrains.clear();
|
|
272
|
+
};
|
|
337
273
|
const writeToPassThrough = (chunk) => {
|
|
338
274
|
if (passThrough.write(chunk) === false) {
|
|
339
275
|
return new Promise((resolve) => {
|
|
276
|
+
const settle = () => {
|
|
277
|
+
pendingDrains.delete(settle);
|
|
278
|
+
resolve();
|
|
279
|
+
};
|
|
280
|
+
pendingDrains.add(settle);
|
|
340
281
|
const withOnce = passThrough;
|
|
341
282
|
if (typeof withOnce.once === "function") {
|
|
342
|
-
withOnce.once("drain",
|
|
283
|
+
withOnce.once("drain", settle);
|
|
343
284
|
} else {
|
|
344
|
-
|
|
285
|
+
settle();
|
|
345
286
|
}
|
|
346
287
|
});
|
|
347
288
|
}
|
|
@@ -365,9 +306,16 @@ function createNodePipeBridge() {
|
|
|
365
306
|
}
|
|
366
307
|
};
|
|
367
308
|
return {
|
|
368
|
-
pipe(writable) {
|
|
309
|
+
pipe(writable, options) {
|
|
369
310
|
piped = true;
|
|
370
311
|
passThrough.pipe(writable);
|
|
312
|
+
const onError = options?.onError;
|
|
313
|
+
if (onError && typeof writable.on === "function") {
|
|
314
|
+
writable.on("error", (err) => {
|
|
315
|
+
flushDrains();
|
|
316
|
+
onError(err);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
371
319
|
if (state === "aborted") {
|
|
372
320
|
destroyPassThrough(abortReason ?? new Error("Stream aborted"));
|
|
373
321
|
return;
|
|
@@ -400,6 +348,7 @@ function createNodePipeBridge() {
|
|
|
400
348
|
state = "aborted";
|
|
401
349
|
abortReason = reason instanceof Error ? reason : new Error("Stream aborted");
|
|
402
350
|
buffer.length = 0;
|
|
351
|
+
flushDrains();
|
|
403
352
|
if (piped) {
|
|
404
353
|
destroyPassThrough(abortReason);
|
|
405
354
|
}
|
|
@@ -429,7 +378,7 @@ function createStreamRuntimeCode(options = {}) {
|
|
|
429
378
|
}
|
|
430
379
|
var FICT_STREAM_RUNTIME_CODE = createStreamRuntimeCode({ observerMode: true });
|
|
431
380
|
|
|
432
|
-
// src/
|
|
381
|
+
// src/render-core.ts
|
|
433
382
|
var DEFAULT_HTML = "<!doctype html><html><head></head><body></body></html>";
|
|
434
383
|
function createSSRDocument(html = DEFAULT_HTML) {
|
|
435
384
|
const window = (0, import_linkedom.parseHTML)(html);
|
|
@@ -580,61 +529,13 @@ function renderToPipeableStream(view, options = {}) {
|
|
|
580
529
|
});
|
|
581
530
|
return {
|
|
582
531
|
pipe(writable) {
|
|
583
|
-
bridge.pipe(writable);
|
|
532
|
+
bridge.pipe(writable, { onError: abort });
|
|
584
533
|
},
|
|
585
534
|
abort,
|
|
586
535
|
shellReady,
|
|
587
536
|
allReady
|
|
588
537
|
};
|
|
589
538
|
}
|
|
590
|
-
function renderToPartial(view, options = {}) {
|
|
591
|
-
const partialOptions = {
|
|
592
|
-
...options,
|
|
593
|
-
mode: "shell",
|
|
594
|
-
fullDocument: options.fullDocument ?? true
|
|
595
|
-
};
|
|
596
|
-
let shell = "";
|
|
597
|
-
let shellPhase = true;
|
|
598
|
-
let abortPartial = null;
|
|
599
|
-
const queued = createQueuedTextStream({
|
|
600
|
-
onCancel(reason) {
|
|
601
|
-
abortPartial?.(reason ?? new Error("Stream canceled"));
|
|
602
|
-
}
|
|
603
|
-
});
|
|
604
|
-
const { shellReady, allReady, abort } = startStreamingRender(
|
|
605
|
-
view,
|
|
606
|
-
partialOptions,
|
|
607
|
-
{
|
|
608
|
-
write(chunk) {
|
|
609
|
-
if (shellPhase) {
|
|
610
|
-
shell += chunk;
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
return queued.writer.write(chunk);
|
|
614
|
-
},
|
|
615
|
-
close() {
|
|
616
|
-
queued.writer.close();
|
|
617
|
-
},
|
|
618
|
-
abort(reason) {
|
|
619
|
-
queued.writer.abort(reason);
|
|
620
|
-
}
|
|
621
|
-
},
|
|
622
|
-
{
|
|
623
|
-
includeTailInShell: true,
|
|
624
|
-
onShellFlushed() {
|
|
625
|
-
shellPhase = false;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
);
|
|
629
|
-
abortPartial = abort;
|
|
630
|
-
return {
|
|
631
|
-
shell,
|
|
632
|
-
stream: queued.stream,
|
|
633
|
-
shellReady,
|
|
634
|
-
allReady,
|
|
635
|
-
abort
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
539
|
function resolveDom(options) {
|
|
639
540
|
if (options.dom) {
|
|
640
541
|
return options.dom;
|
|
@@ -901,12 +802,13 @@ function startStreamingRenderInSession(session, view, options, writer, control =
|
|
|
901
802
|
}
|
|
902
803
|
};
|
|
903
804
|
const abort = (reason) => {
|
|
904
|
-
if (closed) return;
|
|
905
|
-
closed = true;
|
|
906
|
-
writeFailed = true;
|
|
907
|
-
writer.abort(reason);
|
|
908
|
-
cleanup();
|
|
909
805
|
const abortReason = reason ?? new Error("Stream aborted");
|
|
806
|
+
if (!closed) {
|
|
807
|
+
closed = true;
|
|
808
|
+
writeFailed = true;
|
|
809
|
+
writer.abort(reason);
|
|
810
|
+
cleanup();
|
|
811
|
+
}
|
|
910
812
|
rejectShell(abortReason);
|
|
911
813
|
rejectAll(abortReason);
|
|
912
814
|
};
|
|
@@ -1117,7 +1019,6 @@ function serializeDoctype(document, override) {
|
|
|
1117
1019
|
0 && (module.exports = {
|
|
1118
1020
|
createSSRDocument,
|
|
1119
1021
|
renderToDocument,
|
|
1120
|
-
renderToPartial,
|
|
1121
1022
|
renderToPipeableStream,
|
|
1122
1023
|
renderToStream,
|
|
1123
1024
|
renderToString,
|