@nestjs-ssr/react 0.2.6 → 0.3.1
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/cli/init.js +35 -72
- package/dist/cli/init.mjs +35 -72
- package/dist/client.d.mts +55 -304
- package/dist/client.d.ts +55 -304
- package/dist/client.js +360 -13
- package/dist/client.mjs +336 -5
- package/dist/{index-BMdluY1g.d.mts → index-BzOLOiIZ.d.ts} +207 -44
- package/dist/{index-rl0S5kqW.d.ts → index-DdE--mA2.d.mts} +207 -44
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +660 -326
- package/dist/index.mjs +654 -328
- package/dist/render/index.d.mts +2 -2
- package/dist/render/index.d.ts +2 -2
- package/dist/render/index.js +624 -324
- package/dist/render/index.mjs +624 -324
- package/dist/{render-response.interface-Dc-Kwb09.d.mts → render-response.interface-CxbuKGnV.d.mts} +57 -1
- package/dist/{render-response.interface-Dc-Kwb09.d.ts → render-response.interface-CxbuKGnV.d.ts} +57 -1
- package/dist/templates/entry-client.tsx +21 -5
- package/dist/templates/entry-server.tsx +48 -6
- package/dist/use-page-context-05ODF4zW.d.ts +281 -0
- package/dist/use-page-context-CGT9woWe.d.mts +281 -0
- package/package.json +1 -1
- package/src/global.d.ts +11 -0
- package/src/templates/entry-client.tsx +21 -5
- package/src/templates/entry-server.tsx +48 -6
package/dist/render/index.mjs
CHANGED
|
@@ -185,6 +185,153 @@ window.__LAYOUTS__ = ${uneval(layoutMetadata)};
|
|
|
185
185
|
TemplateParserService = _ts_decorate([
|
|
186
186
|
Injectable()
|
|
187
187
|
], TemplateParserService);
|
|
188
|
+
|
|
189
|
+
// src/render/renderers/string-renderer.ts
|
|
190
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
191
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
192
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
193
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
194
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
195
|
+
}
|
|
196
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
197
|
+
function _ts_metadata(k, v) {
|
|
198
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
199
|
+
}
|
|
200
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
201
|
+
var StringRenderer = class _StringRenderer {
|
|
202
|
+
static {
|
|
203
|
+
__name(this, "StringRenderer");
|
|
204
|
+
}
|
|
205
|
+
templateParser;
|
|
206
|
+
logger = new Logger(_StringRenderer.name);
|
|
207
|
+
constructor(templateParser) {
|
|
208
|
+
this.templateParser = templateParser;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Render a React component to a complete HTML string
|
|
212
|
+
*/
|
|
213
|
+
async render(viewComponent, data, context, head) {
|
|
214
|
+
const startTime = Date.now();
|
|
215
|
+
let template = context.template;
|
|
216
|
+
if (context.vite) {
|
|
217
|
+
template = await context.vite.transformIndexHtml("/", template);
|
|
218
|
+
}
|
|
219
|
+
let renderModule;
|
|
220
|
+
if (context.vite) {
|
|
221
|
+
renderModule = await context.vite.ssrLoadModule(context.entryServerPath);
|
|
222
|
+
} else {
|
|
223
|
+
if (context.serverManifest) {
|
|
224
|
+
const manifestEntry = Object.entries(context.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
|
|
225
|
+
if (manifestEntry) {
|
|
226
|
+
const [, entry] = manifestEntry;
|
|
227
|
+
const serverPath = `${process.cwd()}/dist/server/${entry.file}`;
|
|
228
|
+
renderModule = await import(serverPath);
|
|
229
|
+
} else {
|
|
230
|
+
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const { data: pageData, __context: pageContext, __layouts: layouts } = data;
|
|
237
|
+
const appHtml = await renderModule.renderComponent(viewComponent, data);
|
|
238
|
+
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
239
|
+
const layoutMetadata = layouts ? layouts.map((l) => ({
|
|
240
|
+
name: l.layout.displayName || l.layout.name || "default",
|
|
241
|
+
props: l.props
|
|
242
|
+
})) : [];
|
|
243
|
+
const initialStateScript = `
|
|
244
|
+
<script>
|
|
245
|
+
window.__INITIAL_STATE__ = ${uneval(pageData)};
|
|
246
|
+
window.__CONTEXT__ = ${uneval(pageContext)};
|
|
247
|
+
window.__COMPONENT_NAME__ = ${uneval(componentName)};
|
|
248
|
+
window.__LAYOUTS__ = ${uneval(layoutMetadata)};
|
|
249
|
+
</script>
|
|
250
|
+
`;
|
|
251
|
+
let clientScript = "";
|
|
252
|
+
let styles = "";
|
|
253
|
+
if (context.vite) {
|
|
254
|
+
clientScript = `<script type="module" src="/src/views/entry-client.tsx"></script>`;
|
|
255
|
+
} else {
|
|
256
|
+
if (context.manifest) {
|
|
257
|
+
const manifestEntry = Object.entries(context.manifest).find(([key, value]) => value.isEntry && key.includes("entry-client"));
|
|
258
|
+
if (manifestEntry) {
|
|
259
|
+
const [, entry] = manifestEntry;
|
|
260
|
+
const entryFile = entry.file;
|
|
261
|
+
clientScript = `<script type="module" src="/${entryFile}"></script>`;
|
|
262
|
+
if (entry.css) {
|
|
263
|
+
const cssFiles = entry.css;
|
|
264
|
+
styles = cssFiles.map((css) => `<link rel="stylesheet" href="/${css}" />`).join("\n ");
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
this.logger.error("\u26A0\uFE0F Client entry not found in manifest");
|
|
268
|
+
clientScript = `<script type="module" src="/assets/client.js"></script>`;
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
this.logger.error("\u26A0\uFE0F Client manifest not found");
|
|
272
|
+
clientScript = `<script type="module" src="/assets/client.js"></script>`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const headTags = this.templateParser.buildHeadTags(head);
|
|
276
|
+
let html = template.replace("<!--app-html-->", appHtml);
|
|
277
|
+
html = html.replace("<!--initial-state-->", initialStateScript);
|
|
278
|
+
html = html.replace("<!--client-scripts-->", clientScript);
|
|
279
|
+
html = html.replace("<!--styles-->", styles);
|
|
280
|
+
html = html.replace("<!--head-meta-->", headTags);
|
|
281
|
+
if (context.isDevelopment) {
|
|
282
|
+
const duration = Date.now() - startTime;
|
|
283
|
+
const name = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
|
|
284
|
+
this.logger.log(`[SSR] ${name} rendered in ${duration}ms (string mode)`);
|
|
285
|
+
}
|
|
286
|
+
return html;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Render a segment for client-side navigation.
|
|
290
|
+
* Returns just the HTML and metadata without the full page template.
|
|
291
|
+
*/
|
|
292
|
+
async renderSegment(viewComponent, data, context, swapTarget, head) {
|
|
293
|
+
const startTime = Date.now();
|
|
294
|
+
let renderModule;
|
|
295
|
+
if (context.vite) {
|
|
296
|
+
renderModule = await context.vite.ssrLoadModule(context.entryServerPath);
|
|
297
|
+
} else {
|
|
298
|
+
if (context.serverManifest) {
|
|
299
|
+
const manifestEntry = Object.entries(context.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
|
|
300
|
+
if (manifestEntry) {
|
|
301
|
+
const [, entry] = manifestEntry;
|
|
302
|
+
const serverPath = `${process.cwd()}/dist/server/${entry.file}`;
|
|
303
|
+
renderModule = await import(serverPath);
|
|
304
|
+
} else {
|
|
305
|
+
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const { data: pageData, __context: pageContext } = data;
|
|
312
|
+
const html = await renderModule.renderSegment(viewComponent, data);
|
|
313
|
+
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
314
|
+
if (context.isDevelopment) {
|
|
315
|
+
const duration = Date.now() - startTime;
|
|
316
|
+
this.logger.log(`[SSR] ${componentName} segment rendered in ${duration}ms`);
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
html,
|
|
320
|
+
head,
|
|
321
|
+
props: pageData,
|
|
322
|
+
swapTarget,
|
|
323
|
+
componentName,
|
|
324
|
+
context: pageContext
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
StringRenderer = _ts_decorate2([
|
|
329
|
+
Injectable(),
|
|
330
|
+
_ts_metadata("design:type", Function),
|
|
331
|
+
_ts_metadata("design:paramtypes", [
|
|
332
|
+
typeof TemplateParserService === "undefined" ? Object : TemplateParserService
|
|
333
|
+
])
|
|
334
|
+
], StringRenderer);
|
|
188
335
|
function ErrorPageDevelopment({ error, viewPath, phase }) {
|
|
189
336
|
const stackLines = error.stack ? error.stack.split("\n").slice(1) : [];
|
|
190
337
|
return /* @__PURE__ */ React.createElement("html", {
|
|
@@ -300,17 +447,17 @@ function ErrorPageProduction() {
|
|
|
300
447
|
__name(ErrorPageProduction, "ErrorPageProduction");
|
|
301
448
|
|
|
302
449
|
// src/render/streaming-error-handler.ts
|
|
303
|
-
function
|
|
450
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
304
451
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
305
452
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
306
453
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
307
454
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
308
455
|
}
|
|
309
|
-
__name(
|
|
310
|
-
function
|
|
456
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
457
|
+
function _ts_metadata2(k, v) {
|
|
311
458
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
312
459
|
}
|
|
313
|
-
__name(
|
|
460
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
314
461
|
function _ts_param(paramIndex, decorator) {
|
|
315
462
|
return function(target, key) {
|
|
316
463
|
decorator(target, key, paramIndex);
|
|
@@ -334,6 +481,14 @@ var StreamingErrorHandler = class _StreamingErrorHandler {
|
|
|
334
481
|
*/
|
|
335
482
|
handleShellError(error, res, viewPath, isDevelopment) {
|
|
336
483
|
this.logger.error(`Shell error rendering ${viewPath}: ${error.message}`, error.stack);
|
|
484
|
+
if (res.headersSent) {
|
|
485
|
+
this.logger.error(`Cannot send error page for ${viewPath} - headers already sent (streaming started)`);
|
|
486
|
+
if (!res.writableEnded) {
|
|
487
|
+
res.write(this.renderInlineErrorOverlay(error, viewPath, isDevelopment));
|
|
488
|
+
res.end();
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
337
492
|
res.statusCode = 500;
|
|
338
493
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
339
494
|
if (isDevelopment) {
|
|
@@ -369,32 +524,263 @@ var StreamingErrorHandler = class _StreamingErrorHandler {
|
|
|
369
524
|
const element = createElement(ErrorComponent);
|
|
370
525
|
return "<!DOCTYPE html>\n" + renderToStaticMarkup(element);
|
|
371
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* Render inline error overlay for when headers are already sent
|
|
529
|
+
* This gets injected into the stream to show a visible error UI
|
|
530
|
+
*/
|
|
531
|
+
renderInlineErrorOverlay(error, viewPath, isDevelopment) {
|
|
532
|
+
const errorMessage = escapeHtml(error.message);
|
|
533
|
+
const errorStack = escapeHtml(error.stack || "");
|
|
534
|
+
const escapedViewPath = escapeHtml(viewPath);
|
|
535
|
+
if (isDevelopment) {
|
|
536
|
+
return `
|
|
537
|
+
<div id="ssr-error-overlay" style="
|
|
538
|
+
position: fixed;
|
|
539
|
+
inset: 0;
|
|
540
|
+
z-index: 99999;
|
|
541
|
+
background: rgba(0, 0, 0, 0.85);
|
|
542
|
+
color: #fff;
|
|
543
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
544
|
+
font-size: 14px;
|
|
545
|
+
padding: 32px;
|
|
546
|
+
overflow: auto;
|
|
547
|
+
">
|
|
548
|
+
<div style="max-width: 900px; margin: 0 auto;">
|
|
549
|
+
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 24px;">
|
|
550
|
+
<span style="font-size: 32px;">\u26A0\uFE0F</span>
|
|
551
|
+
<h1 style="margin: 0; font-size: 24px; font-weight: 600; color: #ff6b6b;">
|
|
552
|
+
SSR Streaming Error
|
|
553
|
+
</h1>
|
|
554
|
+
</div>
|
|
555
|
+
<p style="color: #aaa; margin-bottom: 16px;">
|
|
556
|
+
An error occurred after streaming started in <code style="background: #333; padding: 2px 6px; border-radius: 4px;">${escapedViewPath}</code>
|
|
557
|
+
</p>
|
|
558
|
+
<div style="background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 16px; margin-bottom: 16px;">
|
|
559
|
+
<div style="color: #ff6b6b; font-weight: 600; margin-bottom: 8px;">Error Message:</div>
|
|
560
|
+
<pre style="margin: 0; white-space: pre-wrap; word-break: break-word; color: #fff;">${errorMessage}</pre>
|
|
561
|
+
</div>
|
|
562
|
+
<div style="background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 16px;">
|
|
563
|
+
<div style="color: #888; font-weight: 600; margin-bottom: 8px;">Stack Trace:</div>
|
|
564
|
+
<pre style="margin: 0; white-space: pre-wrap; word-break: break-word; color: #888; font-size: 12px;">${errorStack}</pre>
|
|
565
|
+
</div>
|
|
566
|
+
<button onclick="document.getElementById('ssr-error-overlay').remove()" style="
|
|
567
|
+
margin-top: 24px;
|
|
568
|
+
background: #333;
|
|
569
|
+
color: #fff;
|
|
570
|
+
border: 1px solid #555;
|
|
571
|
+
padding: 8px 16px;
|
|
572
|
+
border-radius: 6px;
|
|
573
|
+
cursor: pointer;
|
|
574
|
+
font-family: inherit;
|
|
575
|
+
">Dismiss</button>
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
<script>console.error('SSR Streaming Error in ${escapedViewPath}:', ${uneval(error.message)});</script>
|
|
579
|
+
`;
|
|
580
|
+
} else {
|
|
581
|
+
return `
|
|
582
|
+
<div id="ssr-error-overlay" style="
|
|
583
|
+
position: fixed;
|
|
584
|
+
inset: 0;
|
|
585
|
+
z-index: 99999;
|
|
586
|
+
background: #fff;
|
|
587
|
+
color: #333;
|
|
588
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
589
|
+
display: flex;
|
|
590
|
+
align-items: center;
|
|
591
|
+
justify-content: center;
|
|
592
|
+
text-align: center;
|
|
593
|
+
">
|
|
594
|
+
<div>
|
|
595
|
+
<h1 style="font-size: 24px; font-weight: 600; margin-bottom: 16px;">Something went wrong</h1>
|
|
596
|
+
<p style="color: #666; margin-bottom: 24px;">We're sorry, but something went wrong. Please try refreshing the page.</p>
|
|
597
|
+
<button onclick="location.reload()" style="
|
|
598
|
+
background: #333;
|
|
599
|
+
color: #fff;
|
|
600
|
+
border: none;
|
|
601
|
+
padding: 12px 24px;
|
|
602
|
+
border-radius: 6px;
|
|
603
|
+
cursor: pointer;
|
|
604
|
+
font-family: inherit;
|
|
605
|
+
font-size: 16px;
|
|
606
|
+
">Refresh Page</button>
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
`;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
372
612
|
};
|
|
373
|
-
StreamingErrorHandler =
|
|
613
|
+
StreamingErrorHandler = _ts_decorate3([
|
|
374
614
|
Injectable(),
|
|
375
615
|
_ts_param(0, Optional()),
|
|
376
616
|
_ts_param(0, Inject("ERROR_PAGE_DEVELOPMENT")),
|
|
377
617
|
_ts_param(1, Optional()),
|
|
378
618
|
_ts_param(1, Inject("ERROR_PAGE_PRODUCTION")),
|
|
379
|
-
|
|
380
|
-
|
|
619
|
+
_ts_metadata2("design:type", Function),
|
|
620
|
+
_ts_metadata2("design:paramtypes", [
|
|
381
621
|
typeof ComponentType === "undefined" ? Object : ComponentType,
|
|
382
622
|
typeof ComponentType === "undefined" ? Object : ComponentType
|
|
383
623
|
])
|
|
384
624
|
], StreamingErrorHandler);
|
|
385
625
|
|
|
626
|
+
// src/render/renderers/stream-renderer.ts
|
|
627
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
628
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
629
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
630
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
631
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
632
|
+
}
|
|
633
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
634
|
+
function _ts_metadata3(k, v) {
|
|
635
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
636
|
+
}
|
|
637
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
638
|
+
var StreamRenderer = class _StreamRenderer {
|
|
639
|
+
static {
|
|
640
|
+
__name(this, "StreamRenderer");
|
|
641
|
+
}
|
|
642
|
+
templateParser;
|
|
643
|
+
streamingErrorHandler;
|
|
644
|
+
logger = new Logger(_StreamRenderer.name);
|
|
645
|
+
constructor(templateParser, streamingErrorHandler) {
|
|
646
|
+
this.templateParser = templateParser;
|
|
647
|
+
this.streamingErrorHandler = streamingErrorHandler;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Render a React component using streaming SSR
|
|
651
|
+
*
|
|
652
|
+
* @param viewComponent - The React component to render
|
|
653
|
+
* @param data - Data to pass to the component
|
|
654
|
+
* @param res - Express response object (required for streaming)
|
|
655
|
+
* @param context - Render context with Vite and manifest info
|
|
656
|
+
* @param head - Head data for SEO tags
|
|
657
|
+
*/
|
|
658
|
+
async render(viewComponent, data, res, context, head) {
|
|
659
|
+
const startTime = Date.now();
|
|
660
|
+
let shellReadyTime = 0;
|
|
661
|
+
return new Promise((resolve, reject) => {
|
|
662
|
+
const executeStream = /* @__PURE__ */ __name(async () => {
|
|
663
|
+
let template = context.template;
|
|
664
|
+
if (context.vite) {
|
|
665
|
+
template = await context.vite.transformIndexHtml("/", template);
|
|
666
|
+
}
|
|
667
|
+
const templateParts = this.templateParser.parseTemplate(template);
|
|
668
|
+
let renderModule;
|
|
669
|
+
if (context.vite) {
|
|
670
|
+
renderModule = await context.vite.ssrLoadModule(context.entryServerPath);
|
|
671
|
+
} else {
|
|
672
|
+
if (context.serverManifest) {
|
|
673
|
+
const manifestEntry = Object.entries(context.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
|
|
674
|
+
if (manifestEntry) {
|
|
675
|
+
const [, entry] = manifestEntry;
|
|
676
|
+
const serverPath = `${process.cwd()}/dist/server/${entry.file}`;
|
|
677
|
+
renderModule = await import(serverPath);
|
|
678
|
+
} else {
|
|
679
|
+
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
680
|
+
}
|
|
681
|
+
} else {
|
|
682
|
+
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const { data: pageData, __context: pageContext, __layouts: layouts } = data;
|
|
686
|
+
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
687
|
+
const inlineScripts = this.templateParser.buildInlineScripts(pageData, pageContext, componentName, layouts);
|
|
688
|
+
const clientScript = this.templateParser.getClientScriptTag(context.isDevelopment, context.manifest);
|
|
689
|
+
const stylesheetTags = this.templateParser.getStylesheetTags(context.isDevelopment, context.manifest);
|
|
690
|
+
const headTags = this.templateParser.buildHeadTags(head);
|
|
691
|
+
let didError = false;
|
|
692
|
+
let shellErrorOccurred = false;
|
|
693
|
+
const { PassThrough } = await import('stream');
|
|
694
|
+
const reactStream = new PassThrough();
|
|
695
|
+
let allReadyFired = false;
|
|
696
|
+
const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
|
|
697
|
+
onShellReady: /* @__PURE__ */ __name(() => {
|
|
698
|
+
shellReadyTime = Date.now();
|
|
699
|
+
if (!res.headersSent) {
|
|
700
|
+
res.statusCode = didError ? 500 : 200;
|
|
701
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
702
|
+
}
|
|
703
|
+
let htmlStart = templateParts.htmlStart;
|
|
704
|
+
htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
|
|
705
|
+
htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
|
|
706
|
+
res.write(htmlStart);
|
|
707
|
+
res.write(templateParts.rootStart);
|
|
708
|
+
pipe(reactStream);
|
|
709
|
+
reactStream.pipe(res, {
|
|
710
|
+
end: false
|
|
711
|
+
});
|
|
712
|
+
if (context.isDevelopment) {
|
|
713
|
+
const ttfb = shellReadyTime - startTime;
|
|
714
|
+
this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
|
|
715
|
+
}
|
|
716
|
+
}, "onShellReady"),
|
|
717
|
+
onShellError: /* @__PURE__ */ __name((error) => {
|
|
718
|
+
shellErrorOccurred = true;
|
|
719
|
+
this.streamingErrorHandler.handleShellError(error, res, componentName, context.isDevelopment);
|
|
720
|
+
resolve();
|
|
721
|
+
}, "onShellError"),
|
|
722
|
+
onError: /* @__PURE__ */ __name((error) => {
|
|
723
|
+
didError = true;
|
|
724
|
+
this.streamingErrorHandler.handleStreamError(error, componentName);
|
|
725
|
+
}, "onError"),
|
|
726
|
+
onAllReady: /* @__PURE__ */ __name(() => {
|
|
727
|
+
allReadyFired = true;
|
|
728
|
+
}, "onAllReady")
|
|
729
|
+
});
|
|
730
|
+
reactStream.on("end", () => {
|
|
731
|
+
if (shellErrorOccurred) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
res.write(inlineScripts);
|
|
735
|
+
res.write(clientScript);
|
|
736
|
+
res.write(templateParts.rootEnd);
|
|
737
|
+
res.write(templateParts.htmlEnd);
|
|
738
|
+
res.end();
|
|
739
|
+
if (context.isDevelopment) {
|
|
740
|
+
const totalTime = Date.now() - startTime;
|
|
741
|
+
const streamTime = Date.now() - shellReadyTime;
|
|
742
|
+
const viaAllReady = allReadyFired ? " (onAllReady fired)" : " (onAllReady never fired)";
|
|
743
|
+
this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)${viaAllReady}`);
|
|
744
|
+
}
|
|
745
|
+
resolve();
|
|
746
|
+
});
|
|
747
|
+
reactStream.on("error", (error) => {
|
|
748
|
+
reject(error);
|
|
749
|
+
});
|
|
750
|
+
res.on("close", () => {
|
|
751
|
+
abort();
|
|
752
|
+
resolve();
|
|
753
|
+
});
|
|
754
|
+
}, "executeStream");
|
|
755
|
+
executeStream().catch((error) => {
|
|
756
|
+
const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
|
|
757
|
+
this.streamingErrorHandler.handleShellError(error, res, componentName, context.isDevelopment);
|
|
758
|
+
resolve();
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
StreamRenderer = _ts_decorate4([
|
|
764
|
+
Injectable(),
|
|
765
|
+
_ts_metadata3("design:type", Function),
|
|
766
|
+
_ts_metadata3("design:paramtypes", [
|
|
767
|
+
typeof TemplateParserService === "undefined" ? Object : TemplateParserService,
|
|
768
|
+
typeof StreamingErrorHandler === "undefined" ? Object : StreamingErrorHandler
|
|
769
|
+
])
|
|
770
|
+
], StreamRenderer);
|
|
771
|
+
|
|
386
772
|
// src/render/render.service.ts
|
|
387
|
-
function
|
|
773
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
388
774
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
389
775
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
390
776
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
391
777
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
392
778
|
}
|
|
393
|
-
__name(
|
|
394
|
-
function
|
|
779
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
780
|
+
function _ts_metadata4(k, v) {
|
|
395
781
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
396
782
|
}
|
|
397
|
-
__name(
|
|
783
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
398
784
|
function _ts_param2(paramIndex, decorator) {
|
|
399
785
|
return function(target, key) {
|
|
400
786
|
decorator(target, key, paramIndex);
|
|
@@ -405,8 +791,8 @@ var RenderService = class _RenderService {
|
|
|
405
791
|
static {
|
|
406
792
|
__name(this, "RenderService");
|
|
407
793
|
}
|
|
408
|
-
|
|
409
|
-
|
|
794
|
+
stringRenderer;
|
|
795
|
+
streamRenderer;
|
|
410
796
|
defaultHead;
|
|
411
797
|
logger = new Logger(_RenderService.name);
|
|
412
798
|
vite = null;
|
|
@@ -418,12 +804,12 @@ var RenderService = class _RenderService {
|
|
|
418
804
|
entryServerPath;
|
|
419
805
|
rootLayout = void 0;
|
|
420
806
|
rootLayoutChecked = false;
|
|
421
|
-
constructor(
|
|
422
|
-
this.
|
|
423
|
-
this.
|
|
807
|
+
constructor(stringRenderer, streamRenderer, ssrMode, defaultHead, customTemplate) {
|
|
808
|
+
this.stringRenderer = stringRenderer;
|
|
809
|
+
this.streamRenderer = streamRenderer;
|
|
424
810
|
this.defaultHead = defaultHead;
|
|
425
811
|
this.isDevelopment = process.env.NODE_ENV !== "production";
|
|
426
|
-
this.ssrMode = ssrMode || process.env.SSR_MODE || "
|
|
812
|
+
this.ssrMode = ssrMode || process.env.SSR_MODE || "string";
|
|
427
813
|
const absoluteServerPath = join(__dirname, "/templates/entry-server.tsx");
|
|
428
814
|
const relativeServerPath = relative(process.cwd(), absoluteServerPath);
|
|
429
815
|
if (relativeServerPath.startsWith("..")) {
|
|
@@ -431,67 +817,82 @@ var RenderService = class _RenderService {
|
|
|
431
817
|
} else {
|
|
432
818
|
this.entryServerPath = "/" + relativeServerPath.replace(/\\/g, "/");
|
|
433
819
|
}
|
|
820
|
+
this.template = this.loadTemplate(customTemplate);
|
|
821
|
+
if (!this.isDevelopment) {
|
|
822
|
+
this.loadManifests();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Load HTML template from custom path, package, or local location
|
|
827
|
+
*/
|
|
828
|
+
loadTemplate(customTemplate) {
|
|
434
829
|
if (customTemplate) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
830
|
+
return this.loadCustomTemplate(customTemplate);
|
|
831
|
+
}
|
|
832
|
+
return this.loadDefaultTemplate();
|
|
833
|
+
}
|
|
834
|
+
loadCustomTemplate(customTemplate) {
|
|
835
|
+
if (customTemplate.includes("<!DOCTYPE") || customTemplate.includes("<html")) {
|
|
836
|
+
this.logger.log(`\u2713 Loaded custom template (inline)`);
|
|
837
|
+
return customTemplate;
|
|
838
|
+
}
|
|
839
|
+
const customTemplatePath = customTemplate.startsWith("/") ? customTemplate : join(process.cwd(), customTemplate);
|
|
840
|
+
if (!existsSync(customTemplatePath)) {
|
|
841
|
+
throw new Error(`Custom template file not found at ${customTemplatePath}`);
|
|
842
|
+
}
|
|
843
|
+
try {
|
|
844
|
+
const template = readFileSync(customTemplatePath, "utf-8");
|
|
845
|
+
this.logger.log(`\u2713 Loaded custom template from ${customTemplatePath}`);
|
|
846
|
+
return template;
|
|
847
|
+
} catch (error) {
|
|
848
|
+
throw new Error(`Failed to read custom template file at ${customTemplatePath}: ${error.message}`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
loadDefaultTemplate() {
|
|
852
|
+
let templatePath;
|
|
853
|
+
if (this.isDevelopment) {
|
|
854
|
+
const packageTemplatePaths = [
|
|
855
|
+
join(__dirname, "../templates/index.html"),
|
|
856
|
+
join(__dirname, "../src/templates/index.html"),
|
|
857
|
+
join(__dirname, "../../src/templates/index.html")
|
|
858
|
+
];
|
|
859
|
+
const localTemplatePath = join(process.cwd(), "src/views/index.html");
|
|
860
|
+
const foundPackageTemplate = packageTemplatePaths.find((p) => existsSync(p));
|
|
861
|
+
if (foundPackageTemplate) {
|
|
862
|
+
templatePath = foundPackageTemplate;
|
|
863
|
+
} else if (existsSync(localTemplatePath)) {
|
|
864
|
+
templatePath = localTemplatePath;
|
|
438
865
|
} else {
|
|
439
|
-
|
|
440
|
-
if (!existsSync(customTemplatePath)) {
|
|
441
|
-
throw new Error(`Custom template file not found at ${customTemplatePath}`);
|
|
442
|
-
}
|
|
443
|
-
try {
|
|
444
|
-
this.template = readFileSync(customTemplatePath, "utf-8");
|
|
445
|
-
this.logger.log(`\u2713 Loaded custom template from ${customTemplatePath}`);
|
|
446
|
-
} catch (error) {
|
|
447
|
-
throw new Error(`Failed to read custom template file at ${customTemplatePath}: ${error.message}`);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
} else {
|
|
451
|
-
let templatePath;
|
|
452
|
-
if (this.isDevelopment) {
|
|
453
|
-
const packageTemplatePaths = [
|
|
454
|
-
join(__dirname, "../templates/index.html"),
|
|
455
|
-
join(__dirname, "../src/templates/index.html"),
|
|
456
|
-
join(__dirname, "../../src/templates/index.html")
|
|
457
|
-
];
|
|
458
|
-
const localTemplatePath = join(process.cwd(), "src/views/index.html");
|
|
459
|
-
const foundPackageTemplate = packageTemplatePaths.find((p) => existsSync(p));
|
|
460
|
-
if (foundPackageTemplate) {
|
|
461
|
-
templatePath = foundPackageTemplate;
|
|
462
|
-
} else if (existsSync(localTemplatePath)) {
|
|
463
|
-
templatePath = localTemplatePath;
|
|
464
|
-
} else {
|
|
465
|
-
throw new Error(`Template file not found. Tried:
|
|
866
|
+
throw new Error(`Template file not found. Tried:
|
|
466
867
|
` + packageTemplatePaths.map((p) => ` - ${p} (package template)`).join("\n") + `
|
|
467
868
|
- ${localTemplatePath} (local template)`);
|
|
468
|
-
}
|
|
469
|
-
} else {
|
|
470
|
-
templatePath = join(process.cwd(), "dist/client/index.html");
|
|
471
|
-
if (!existsSync(templatePath)) {
|
|
472
|
-
throw new Error(`Template file not found at ${templatePath}. Make sure to run the build process first.`);
|
|
473
|
-
}
|
|
474
869
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
throw new Error(`Failed to read template file at ${templatePath}: ${error.message}`);
|
|
870
|
+
} else {
|
|
871
|
+
templatePath = join(process.cwd(), "dist/client/index.html");
|
|
872
|
+
if (!existsSync(templatePath)) {
|
|
873
|
+
throw new Error(`Template file not found at ${templatePath}. Make sure to run the build process first.`);
|
|
480
874
|
}
|
|
481
875
|
}
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
876
|
+
try {
|
|
877
|
+
const template = readFileSync(templatePath, "utf-8");
|
|
878
|
+
this.logger.log(`\u2713 Loaded template from ${templatePath}`);
|
|
879
|
+
return template;
|
|
880
|
+
} catch (error) {
|
|
881
|
+
throw new Error(`Failed to read template file at ${templatePath}: ${error.message}`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
loadManifests() {
|
|
885
|
+
const manifestPath = join(process.cwd(), "dist/client/.vite/manifest.json");
|
|
886
|
+
if (existsSync(manifestPath)) {
|
|
887
|
+
this.manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
888
|
+
} else {
|
|
889
|
+
this.logger.warn("\u26A0\uFE0F Client manifest not found. Run `pnpm build:client` first.");
|
|
890
|
+
}
|
|
891
|
+
const serverManifestPath = join(process.cwd(), "dist/server/.vite/manifest.json");
|
|
892
|
+
if (existsSync(serverManifestPath)) {
|
|
893
|
+
this.serverManifest = JSON.parse(readFileSync(serverManifestPath, "utf-8"));
|
|
894
|
+
} else {
|
|
895
|
+
this.logger.warn("\u26A0\uFE0F Server manifest not found. Run `pnpm build:server` first.");
|
|
495
896
|
}
|
|
496
897
|
}
|
|
497
898
|
setViteServer(vite) {
|
|
@@ -545,16 +946,50 @@ var RenderService = class _RenderService {
|
|
|
545
946
|
}
|
|
546
947
|
/**
|
|
547
948
|
* Main render method that routes to string or stream mode
|
|
949
|
+
*
|
|
950
|
+
* String mode (default):
|
|
951
|
+
* - Returns complete HTML string
|
|
952
|
+
* - Atomic responses - works completely or fails completely
|
|
953
|
+
* - Proper HTTP status codes always
|
|
954
|
+
*
|
|
955
|
+
* Stream mode:
|
|
956
|
+
* - Writes directly to response
|
|
957
|
+
* - Better TTFB, progressive rendering
|
|
958
|
+
* - Requires response object
|
|
548
959
|
*/
|
|
549
960
|
async render(viewComponent, data = {}, res, head) {
|
|
550
961
|
const mergedHead = this.mergeHead(this.defaultHead, head);
|
|
962
|
+
const renderContext = {
|
|
963
|
+
template: this.template,
|
|
964
|
+
vite: this.vite,
|
|
965
|
+
manifest: this.manifest,
|
|
966
|
+
serverManifest: this.serverManifest,
|
|
967
|
+
entryServerPath: this.entryServerPath,
|
|
968
|
+
isDevelopment: this.isDevelopment
|
|
969
|
+
};
|
|
551
970
|
if (this.ssrMode === "stream") {
|
|
552
971
|
if (!res) {
|
|
553
972
|
throw new Error("Response object is required for streaming SSR mode. Pass res as third parameter.");
|
|
554
973
|
}
|
|
555
|
-
return this.
|
|
974
|
+
return this.streamRenderer.render(viewComponent, data, res, renderContext, mergedHead);
|
|
556
975
|
}
|
|
557
|
-
return this.
|
|
976
|
+
return this.stringRenderer.render(viewComponent, data, renderContext, mergedHead);
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Render a segment for client-side navigation.
|
|
980
|
+
* Always uses string mode (streaming not supported for segments).
|
|
981
|
+
*/
|
|
982
|
+
async renderSegment(viewComponent, data, swapTarget, head) {
|
|
983
|
+
const mergedHead = this.mergeHead(this.defaultHead, head);
|
|
984
|
+
const renderContext = {
|
|
985
|
+
template: this.template,
|
|
986
|
+
vite: this.vite,
|
|
987
|
+
manifest: this.manifest,
|
|
988
|
+
serverManifest: this.serverManifest,
|
|
989
|
+
entryServerPath: this.entryServerPath,
|
|
990
|
+
isDevelopment: this.isDevelopment
|
|
991
|
+
};
|
|
992
|
+
return this.stringRenderer.renderSegment(viewComponent, data, renderContext, swapTarget, mergedHead);
|
|
558
993
|
}
|
|
559
994
|
/**
|
|
560
995
|
* Merge default head with page-specific head
|
|
@@ -567,7 +1002,6 @@ var RenderService = class _RenderService {
|
|
|
567
1002
|
return {
|
|
568
1003
|
...defaultHead,
|
|
569
1004
|
...pageHead,
|
|
570
|
-
// Merge arrays (links and meta) instead of replacing
|
|
571
1005
|
links: [
|
|
572
1006
|
...defaultHead?.links || [],
|
|
573
1007
|
...pageHead?.links || []
|
|
@@ -578,198 +1012,8 @@ var RenderService = class _RenderService {
|
|
|
578
1012
|
]
|
|
579
1013
|
};
|
|
580
1014
|
}
|
|
581
|
-
/**
|
|
582
|
-
* Traditional string-based SSR using renderToString
|
|
583
|
-
*/
|
|
584
|
-
async renderToString(viewComponent, data = {}, head) {
|
|
585
|
-
const startTime = Date.now();
|
|
586
|
-
try {
|
|
587
|
-
let template = this.template;
|
|
588
|
-
if (this.vite) {
|
|
589
|
-
template = await this.vite.transformIndexHtml("/", template);
|
|
590
|
-
}
|
|
591
|
-
let renderModule;
|
|
592
|
-
if (this.vite) {
|
|
593
|
-
renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
|
|
594
|
-
} else {
|
|
595
|
-
if (this.serverManifest) {
|
|
596
|
-
const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
|
|
597
|
-
if (manifestEntry) {
|
|
598
|
-
const [, entry] = manifestEntry;
|
|
599
|
-
const serverPath = join(process.cwd(), "dist/server", entry.file);
|
|
600
|
-
renderModule = await import(serverPath);
|
|
601
|
-
} else {
|
|
602
|
-
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
603
|
-
}
|
|
604
|
-
} else {
|
|
605
|
-
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
const { data: pageData, __context: context, __layouts: layouts } = data;
|
|
609
|
-
const appHtml = await renderModule.renderComponent(viewComponent, data);
|
|
610
|
-
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
611
|
-
const layoutMetadata = layouts ? layouts.map((l) => ({
|
|
612
|
-
name: l.layout.displayName || l.layout.name || "default",
|
|
613
|
-
props: l.props
|
|
614
|
-
})) : [];
|
|
615
|
-
const initialStateScript = `
|
|
616
|
-
<script>
|
|
617
|
-
window.__INITIAL_STATE__ = ${uneval(pageData)};
|
|
618
|
-
window.__CONTEXT__ = ${uneval(context)};
|
|
619
|
-
window.__COMPONENT_NAME__ = ${uneval(componentName)};
|
|
620
|
-
window.__LAYOUTS__ = ${uneval(layoutMetadata)};
|
|
621
|
-
</script>
|
|
622
|
-
`;
|
|
623
|
-
let clientScript = "";
|
|
624
|
-
let styles = "";
|
|
625
|
-
if (this.vite) {
|
|
626
|
-
clientScript = `<script type="module" src="/src/views/entry-client.tsx"></script>`;
|
|
627
|
-
styles = "";
|
|
628
|
-
} else {
|
|
629
|
-
if (this.manifest) {
|
|
630
|
-
const manifestEntry = Object.entries(this.manifest).find(([key, value]) => value.isEntry && key.includes("entry-client"));
|
|
631
|
-
if (manifestEntry) {
|
|
632
|
-
const [, entry] = manifestEntry;
|
|
633
|
-
const entryFile = entry.file;
|
|
634
|
-
clientScript = `<script type="module" src="/${entryFile}"></script>`;
|
|
635
|
-
if (entry.css) {
|
|
636
|
-
const cssFiles = entry.css;
|
|
637
|
-
styles = cssFiles.map((css) => `<link rel="stylesheet" href="/${css}" />`).join("\n ");
|
|
638
|
-
}
|
|
639
|
-
} else {
|
|
640
|
-
this.logger.error("\u26A0\uFE0F Client entry not found in manifest");
|
|
641
|
-
clientScript = `<script type="module" src="/assets/client.js"></script>`;
|
|
642
|
-
}
|
|
643
|
-
} else {
|
|
644
|
-
this.logger.error("\u26A0\uFE0F Client manifest not found");
|
|
645
|
-
clientScript = `<script type="module" src="/assets/client.js"></script>`;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
const headTags = this.templateParser.buildHeadTags(head);
|
|
649
|
-
let html = template.replace("<!--app-html-->", appHtml);
|
|
650
|
-
html = html.replace("<!--initial-state-->", initialStateScript);
|
|
651
|
-
html = html.replace("<!--client-scripts-->", clientScript);
|
|
652
|
-
html = html.replace("<!--styles-->", styles);
|
|
653
|
-
html = html.replace("<!--head-meta-->", headTags);
|
|
654
|
-
if (this.isDevelopment) {
|
|
655
|
-
const duration = Date.now() - startTime;
|
|
656
|
-
const componentName2 = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
|
|
657
|
-
this.logger.log(`[SSR] ${componentName2} rendered in ${duration}ms (string mode)`);
|
|
658
|
-
}
|
|
659
|
-
return html;
|
|
660
|
-
} catch (error) {
|
|
661
|
-
throw error;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* Modern streaming SSR using renderToPipeableStream
|
|
666
|
-
*/
|
|
667
|
-
async renderToStream(viewComponent, data = {}, res, head) {
|
|
668
|
-
const startTime = Date.now();
|
|
669
|
-
let shellReadyTime = 0;
|
|
670
|
-
return new Promise((resolve, reject) => {
|
|
671
|
-
const executeStream = /* @__PURE__ */ __name(async () => {
|
|
672
|
-
let template = this.template;
|
|
673
|
-
if (this.vite) {
|
|
674
|
-
template = await this.vite.transformIndexHtml("/", template);
|
|
675
|
-
}
|
|
676
|
-
const templateParts = this.templateParser.parseTemplate(template);
|
|
677
|
-
let renderModule;
|
|
678
|
-
if (this.vite) {
|
|
679
|
-
renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
|
|
680
|
-
} else {
|
|
681
|
-
if (this.serverManifest) {
|
|
682
|
-
const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
|
|
683
|
-
if (manifestEntry) {
|
|
684
|
-
const [, entry] = manifestEntry;
|
|
685
|
-
const serverPath = join(process.cwd(), "dist/server", entry.file);
|
|
686
|
-
renderModule = await import(serverPath);
|
|
687
|
-
} else {
|
|
688
|
-
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
689
|
-
}
|
|
690
|
-
} else {
|
|
691
|
-
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
const { data: pageData, __context: context, __layouts: layouts } = data;
|
|
695
|
-
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
696
|
-
const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName, layouts);
|
|
697
|
-
const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
|
|
698
|
-
const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
|
|
699
|
-
const headTags = this.templateParser.buildHeadTags(head);
|
|
700
|
-
let didError = false;
|
|
701
|
-
let shellErrorOccurred = false;
|
|
702
|
-
const { PassThrough } = await import('stream');
|
|
703
|
-
const reactStream = new PassThrough();
|
|
704
|
-
let allReadyFired = false;
|
|
705
|
-
const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
|
|
706
|
-
onShellReady: /* @__PURE__ */ __name(() => {
|
|
707
|
-
shellReadyTime = Date.now();
|
|
708
|
-
if (!res.headersSent) {
|
|
709
|
-
res.statusCode = didError ? 500 : 200;
|
|
710
|
-
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
711
|
-
}
|
|
712
|
-
let htmlStart = templateParts.htmlStart;
|
|
713
|
-
htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
|
|
714
|
-
htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
|
|
715
|
-
res.write(htmlStart);
|
|
716
|
-
res.write(templateParts.rootStart);
|
|
717
|
-
pipe(reactStream);
|
|
718
|
-
reactStream.pipe(res, {
|
|
719
|
-
end: false
|
|
720
|
-
});
|
|
721
|
-
if (this.isDevelopment) {
|
|
722
|
-
const ttfb = shellReadyTime - startTime;
|
|
723
|
-
this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
|
|
724
|
-
}
|
|
725
|
-
}, "onShellReady"),
|
|
726
|
-
onShellError: /* @__PURE__ */ __name((error) => {
|
|
727
|
-
shellErrorOccurred = true;
|
|
728
|
-
this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
|
|
729
|
-
resolve();
|
|
730
|
-
}, "onShellError"),
|
|
731
|
-
onError: /* @__PURE__ */ __name((error) => {
|
|
732
|
-
didError = true;
|
|
733
|
-
this.streamingErrorHandler.handleStreamError(error, componentName);
|
|
734
|
-
}, "onError"),
|
|
735
|
-
onAllReady: /* @__PURE__ */ __name(() => {
|
|
736
|
-
allReadyFired = true;
|
|
737
|
-
}, "onAllReady")
|
|
738
|
-
});
|
|
739
|
-
reactStream.on("end", () => {
|
|
740
|
-
if (shellErrorOccurred) {
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
res.write(inlineScripts);
|
|
744
|
-
res.write(clientScript);
|
|
745
|
-
res.write(templateParts.rootEnd);
|
|
746
|
-
res.write(templateParts.htmlEnd);
|
|
747
|
-
res.end();
|
|
748
|
-
if (this.isDevelopment) {
|
|
749
|
-
const totalTime = Date.now() - startTime;
|
|
750
|
-
const streamTime = Date.now() - shellReadyTime;
|
|
751
|
-
const viaAllReady = allReadyFired ? " (onAllReady fired)" : " (onAllReady never fired)";
|
|
752
|
-
this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)${viaAllReady}`);
|
|
753
|
-
}
|
|
754
|
-
resolve();
|
|
755
|
-
});
|
|
756
|
-
reactStream.on("error", (error) => {
|
|
757
|
-
reject(error);
|
|
758
|
-
});
|
|
759
|
-
res.on("close", () => {
|
|
760
|
-
abort();
|
|
761
|
-
resolve();
|
|
762
|
-
});
|
|
763
|
-
}, "executeStream");
|
|
764
|
-
executeStream().catch((error) => {
|
|
765
|
-
const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
|
|
766
|
-
this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
|
|
767
|
-
resolve();
|
|
768
|
-
});
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
1015
|
};
|
|
772
|
-
RenderService =
|
|
1016
|
+
RenderService = _ts_decorate5([
|
|
773
1017
|
Injectable(),
|
|
774
1018
|
_ts_param2(2, Optional()),
|
|
775
1019
|
_ts_param2(2, Inject("SSR_MODE")),
|
|
@@ -777,10 +1021,10 @@ RenderService = _ts_decorate3([
|
|
|
777
1021
|
_ts_param2(3, Inject("DEFAULT_HEAD")),
|
|
778
1022
|
_ts_param2(4, Optional()),
|
|
779
1023
|
_ts_param2(4, Inject("CUSTOM_TEMPLATE")),
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
typeof
|
|
783
|
-
typeof
|
|
1024
|
+
_ts_metadata4("design:type", Function),
|
|
1025
|
+
_ts_metadata4("design:paramtypes", [
|
|
1026
|
+
typeof StringRenderer === "undefined" ? Object : StringRenderer,
|
|
1027
|
+
typeof StreamRenderer === "undefined" ? Object : StreamRenderer,
|
|
784
1028
|
typeof SSRMode === "undefined" ? Object : SSRMode,
|
|
785
1029
|
typeof HeadData === "undefined" ? Object : HeadData,
|
|
786
1030
|
String
|
|
@@ -791,17 +1035,17 @@ var RENDER_OPTIONS_KEY = "render_options";
|
|
|
791
1035
|
var LAYOUT_KEY = "layout";
|
|
792
1036
|
|
|
793
1037
|
// src/render/render.interceptor.ts
|
|
794
|
-
function
|
|
1038
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
795
1039
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
796
1040
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
797
1041
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
798
1042
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
799
1043
|
}
|
|
800
|
-
__name(
|
|
801
|
-
function
|
|
1044
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
1045
|
+
function _ts_metadata5(k, v) {
|
|
802
1046
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
803
1047
|
}
|
|
804
|
-
__name(
|
|
1048
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
805
1049
|
function _ts_param3(paramIndex, decorator) {
|
|
806
1050
|
return function(target, key) {
|
|
807
1051
|
decorator(target, key, paramIndex);
|
|
@@ -851,15 +1095,20 @@ var RenderInterceptor = class {
|
|
|
851
1095
|
} else if (renderOptions?.layout === false) {
|
|
852
1096
|
return layouts;
|
|
853
1097
|
}
|
|
854
|
-
if (controllerLayoutMeta) {
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1098
|
+
if (controllerLayoutMeta?.layout) {
|
|
1099
|
+
const rootLayoutName = rootLayout?.displayName || rootLayout?.name;
|
|
1100
|
+
const controllerLayoutName = controllerLayoutMeta.layout.displayName || controllerLayoutMeta.layout.name;
|
|
1101
|
+
const isDuplicateOfRoot = rootLayout && rootLayoutName && controllerLayoutName === rootLayoutName;
|
|
1102
|
+
if (!isDuplicateOfRoot) {
|
|
1103
|
+
const mergedProps = {
|
|
1104
|
+
...controllerLayoutMeta.options?.props || {},
|
|
1105
|
+
...dynamicLayoutProps || {}
|
|
1106
|
+
};
|
|
1107
|
+
layouts.push({
|
|
1108
|
+
layout: controllerLayoutMeta.layout,
|
|
1109
|
+
props: mergedProps
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
863
1112
|
}
|
|
864
1113
|
if (renderOptions?.layout) {
|
|
865
1114
|
const mergedProps = {
|
|
@@ -873,6 +1122,53 @@ var RenderInterceptor = class {
|
|
|
873
1122
|
}
|
|
874
1123
|
return layouts;
|
|
875
1124
|
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Detect request type based on headers.
|
|
1127
|
+
* - If X-Current-Layouts header is present, this is a segment request
|
|
1128
|
+
* - Only GET requests can be segments
|
|
1129
|
+
*/
|
|
1130
|
+
detectRequestType(request) {
|
|
1131
|
+
if (request.method !== "GET") {
|
|
1132
|
+
return {
|
|
1133
|
+
type: "full"
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
const layoutsHeader = request.headers["x-current-layouts"];
|
|
1137
|
+
if (layoutsHeader && typeof layoutsHeader === "string") {
|
|
1138
|
+
const currentLayouts = layoutsHeader.split(",").map((s) => s.trim());
|
|
1139
|
+
return {
|
|
1140
|
+
type: "segment",
|
|
1141
|
+
currentLayouts
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
return {
|
|
1145
|
+
type: "full"
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Determine swap target by finding deepest common layout.
|
|
1150
|
+
* Returns null if no common ancestor (client should do full navigation).
|
|
1151
|
+
*/
|
|
1152
|
+
determineSwapTarget(currentLayouts, targetLayouts) {
|
|
1153
|
+
const targetNames = targetLayouts.map((l) => l.layout.displayName || l.layout.name);
|
|
1154
|
+
let commonLayout = null;
|
|
1155
|
+
for (let i = 0; i < Math.min(currentLayouts.length, targetNames.length); i++) {
|
|
1156
|
+
if (currentLayouts[i] === targetNames[i]) {
|
|
1157
|
+
commonLayout = currentLayouts[i];
|
|
1158
|
+
} else {
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return commonLayout;
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Filter layouts to only include those below the swap target.
|
|
1166
|
+
* The swap target's outlet will contain the filtered layouts.
|
|
1167
|
+
*/
|
|
1168
|
+
filterLayoutsFromSwapTarget(layouts, swapTarget) {
|
|
1169
|
+
const index = layouts.findIndex((l) => (l.layout.displayName || l.layout.name) === swapTarget);
|
|
1170
|
+
return index >= 0 ? layouts.slice(index + 1) : layouts;
|
|
1171
|
+
}
|
|
876
1172
|
intercept(context, next) {
|
|
877
1173
|
const viewPathOrComponent = this.reflector.get(RENDER_KEY, context.getHandler());
|
|
878
1174
|
if (!viewPathOrComponent) {
|
|
@@ -921,6 +1217,24 @@ var RenderInterceptor = class {
|
|
|
921
1217
|
__context: renderContext,
|
|
922
1218
|
__layouts: layoutChain
|
|
923
1219
|
};
|
|
1220
|
+
const { type, currentLayouts } = this.detectRequestType(request);
|
|
1221
|
+
if (type === "segment" && currentLayouts) {
|
|
1222
|
+
const swapTarget = this.determineSwapTarget(currentLayouts, layoutChain);
|
|
1223
|
+
if (!swapTarget) {
|
|
1224
|
+
response.type("application/json");
|
|
1225
|
+
return {
|
|
1226
|
+
swapTarget: null
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
const filteredLayouts = this.filterLayoutsFromSwapTarget(layoutChain, swapTarget);
|
|
1230
|
+
const segmentData = {
|
|
1231
|
+
...fullData,
|
|
1232
|
+
__layouts: filteredLayouts
|
|
1233
|
+
};
|
|
1234
|
+
const result = await this.renderService.renderSegment(viewPathOrComponent, segmentData, swapTarget, renderResponse.head);
|
|
1235
|
+
response.type("application/json");
|
|
1236
|
+
return result;
|
|
1237
|
+
}
|
|
924
1238
|
try {
|
|
925
1239
|
const html = await this.renderService.render(viewPathOrComponent, fullData, response, renderResponse.head);
|
|
926
1240
|
if (html !== void 0) {
|
|
@@ -934,31 +1248,31 @@ var RenderInterceptor = class {
|
|
|
934
1248
|
}));
|
|
935
1249
|
}
|
|
936
1250
|
};
|
|
937
|
-
RenderInterceptor =
|
|
1251
|
+
RenderInterceptor = _ts_decorate6([
|
|
938
1252
|
Injectable(),
|
|
939
1253
|
_ts_param3(2, Optional()),
|
|
940
1254
|
_ts_param3(2, Inject("ALLOWED_HEADERS")),
|
|
941
1255
|
_ts_param3(3, Optional()),
|
|
942
1256
|
_ts_param3(3, Inject("ALLOWED_COOKIES")),
|
|
943
|
-
|
|
944
|
-
|
|
1257
|
+
_ts_metadata5("design:type", Function),
|
|
1258
|
+
_ts_metadata5("design:paramtypes", [
|
|
945
1259
|
typeof Reflector === "undefined" ? Object : Reflector,
|
|
946
1260
|
typeof RenderService === "undefined" ? Object : RenderService,
|
|
947
1261
|
Array,
|
|
948
1262
|
Array
|
|
949
1263
|
])
|
|
950
1264
|
], RenderInterceptor);
|
|
951
|
-
function
|
|
1265
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
952
1266
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
953
1267
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
954
1268
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
955
1269
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
956
1270
|
}
|
|
957
|
-
__name(
|
|
958
|
-
function
|
|
1271
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
1272
|
+
function _ts_metadata6(k, v) {
|
|
959
1273
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
960
1274
|
}
|
|
961
|
-
__name(
|
|
1275
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
962
1276
|
function _ts_param4(paramIndex, decorator) {
|
|
963
1277
|
return function(target, key) {
|
|
964
1278
|
decorator(target, key, paramIndex);
|
|
@@ -972,14 +1286,12 @@ var ViteInitializerService = class _ViteInitializerService {
|
|
|
972
1286
|
renderService;
|
|
973
1287
|
httpAdapterHost;
|
|
974
1288
|
logger = new Logger(_ViteInitializerService.name);
|
|
975
|
-
viteMode;
|
|
976
1289
|
vitePort;
|
|
977
1290
|
viteServer = null;
|
|
978
1291
|
isShuttingDown = false;
|
|
979
1292
|
constructor(renderService, httpAdapterHost, viteConfig) {
|
|
980
1293
|
this.renderService = renderService;
|
|
981
1294
|
this.httpAdapterHost = httpAdapterHost;
|
|
982
|
-
this.viteMode = viteConfig?.mode || "embedded";
|
|
983
1295
|
this.vitePort = viteConfig?.port || 5173;
|
|
984
1296
|
this.registerSignalHandlers();
|
|
985
1297
|
}
|
|
@@ -1011,30 +1323,12 @@ var ViteInitializerService = class _ViteInitializerService {
|
|
|
1011
1323
|
appType: "custom"
|
|
1012
1324
|
});
|
|
1013
1325
|
this.renderService.setViteServer(this.viteServer);
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
} else if (this.viteMode === "proxy") {
|
|
1017
|
-
await this.setupViteProxy();
|
|
1018
|
-
}
|
|
1019
|
-
this.logger.log(`\u2713 Vite initialized for SSR (mode: ${this.viteMode})`);
|
|
1326
|
+
await this.setupViteProxy();
|
|
1327
|
+
this.logger.log("\u2713 Vite initialized for SSR");
|
|
1020
1328
|
} catch (error) {
|
|
1021
1329
|
this.logger.warn(`Failed to initialize Vite: ${error.message}. Make sure vite is installed.`);
|
|
1022
1330
|
}
|
|
1023
1331
|
}
|
|
1024
|
-
async mountViteMiddleware(vite) {
|
|
1025
|
-
try {
|
|
1026
|
-
const httpAdapter = this.httpAdapterHost.httpAdapter;
|
|
1027
|
-
if (!httpAdapter) {
|
|
1028
|
-
this.logger.warn("HTTP adapter not available, skipping Vite middleware setup");
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
const app = httpAdapter.getInstance();
|
|
1032
|
-
app.use(vite.middlewares);
|
|
1033
|
-
this.logger.log(`\u2713 Vite middleware mounted (embedded mode with HMR)`);
|
|
1034
|
-
} catch (error) {
|
|
1035
|
-
this.logger.warn(`Failed to mount Vite middleware: ${error.message}`);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
1332
|
async setupViteProxy() {
|
|
1039
1333
|
try {
|
|
1040
1334
|
const httpAdapter = this.httpAdapterHost.httpAdapter;
|
|
@@ -1053,7 +1347,7 @@ var ViteInitializerService = class _ViteInitializerService {
|
|
|
1053
1347
|
}, "pathFilter")
|
|
1054
1348
|
});
|
|
1055
1349
|
app.use(viteProxy);
|
|
1056
|
-
this.logger.log(`\u2713 Vite HMR proxy configured (
|
|
1350
|
+
this.logger.log(`\u2713 Vite HMR proxy configured (Vite dev server on port ${this.vitePort})`);
|
|
1057
1351
|
} catch (error) {
|
|
1058
1352
|
this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
|
|
1059
1353
|
}
|
|
@@ -1104,12 +1398,12 @@ var ViteInitializerService = class _ViteInitializerService {
|
|
|
1104
1398
|
}
|
|
1105
1399
|
}
|
|
1106
1400
|
};
|
|
1107
|
-
ViteInitializerService =
|
|
1401
|
+
ViteInitializerService = _ts_decorate7([
|
|
1108
1402
|
Injectable(),
|
|
1109
1403
|
_ts_param4(2, Optional()),
|
|
1110
1404
|
_ts_param4(2, Inject("VITE_CONFIG")),
|
|
1111
|
-
|
|
1112
|
-
|
|
1405
|
+
_ts_metadata6("design:type", Function),
|
|
1406
|
+
_ts_metadata6("design:paramtypes", [
|
|
1113
1407
|
typeof RenderService === "undefined" ? Object : RenderService,
|
|
1114
1408
|
typeof HttpAdapterHost === "undefined" ? Object : HttpAdapterHost,
|
|
1115
1409
|
typeof ViteConfig === "undefined" ? Object : ViteConfig
|
|
@@ -1117,13 +1411,13 @@ ViteInitializerService = _ts_decorate5([
|
|
|
1117
1411
|
], ViteInitializerService);
|
|
1118
1412
|
|
|
1119
1413
|
// src/render/render.module.ts
|
|
1120
|
-
function
|
|
1414
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
1121
1415
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1122
1416
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1123
1417
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1124
1418
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1125
1419
|
}
|
|
1126
|
-
__name(
|
|
1420
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
1127
1421
|
var RenderModule = class _RenderModule {
|
|
1128
1422
|
static {
|
|
1129
1423
|
__name(this, "RenderModule");
|
|
@@ -1136,17 +1430,17 @@ var RenderModule = class _RenderModule {
|
|
|
1136
1430
|
*
|
|
1137
1431
|
* @example
|
|
1138
1432
|
* ```ts
|
|
1139
|
-
* // Zero config - uses
|
|
1433
|
+
* // Zero config - uses string mode (default, recommended)
|
|
1140
1434
|
* RenderModule.forRoot()
|
|
1141
1435
|
*
|
|
1142
|
-
* //
|
|
1143
|
-
* RenderModule.forRoot({ mode: 'stream' })
|
|
1144
|
-
*
|
|
1145
|
-
* // Enable HMR with proxy mode
|
|
1436
|
+
* // Custom Vite port
|
|
1146
1437
|
* RenderModule.forRoot({
|
|
1147
|
-
* vite: {
|
|
1438
|
+
* vite: { port: 3001 }
|
|
1148
1439
|
* })
|
|
1149
1440
|
*
|
|
1441
|
+
* // Enable streaming SSR (advanced - see mode docs for trade-offs)
|
|
1442
|
+
* RenderModule.forRoot({ mode: 'stream' })
|
|
1443
|
+
*
|
|
1150
1444
|
* // Custom error pages
|
|
1151
1445
|
* RenderModule.forRoot({
|
|
1152
1446
|
* errorPageDevelopment: DevErrorPage,
|
|
@@ -1160,6 +1454,8 @@ var RenderModule = class _RenderModule {
|
|
|
1160
1454
|
TemplateParserService,
|
|
1161
1455
|
StreamingErrorHandler,
|
|
1162
1456
|
ViteInitializerService,
|
|
1457
|
+
StringRenderer,
|
|
1458
|
+
StreamRenderer,
|
|
1163
1459
|
{
|
|
1164
1460
|
provide: APP_INTERCEPTOR,
|
|
1165
1461
|
useClass: RenderInterceptor
|
|
@@ -1264,6 +1560,8 @@ var RenderModule = class _RenderModule {
|
|
|
1264
1560
|
TemplateParserService,
|
|
1265
1561
|
StreamingErrorHandler,
|
|
1266
1562
|
ViteInitializerService,
|
|
1563
|
+
StringRenderer,
|
|
1564
|
+
StreamRenderer,
|
|
1267
1565
|
{
|
|
1268
1566
|
provide: APP_INTERCEPTOR,
|
|
1269
1567
|
useClass: RenderInterceptor
|
|
@@ -1348,7 +1646,7 @@ var RenderModule = class _RenderModule {
|
|
|
1348
1646
|
return this.forRootAsync(options);
|
|
1349
1647
|
}
|
|
1350
1648
|
};
|
|
1351
|
-
RenderModule =
|
|
1649
|
+
RenderModule = _ts_decorate8([
|
|
1352
1650
|
Global(),
|
|
1353
1651
|
Module({
|
|
1354
1652
|
providers: [
|
|
@@ -1356,6 +1654,8 @@ RenderModule = _ts_decorate6([
|
|
|
1356
1654
|
TemplateParserService,
|
|
1357
1655
|
StreamingErrorHandler,
|
|
1358
1656
|
ViteInitializerService,
|
|
1657
|
+
StringRenderer,
|
|
1658
|
+
StreamRenderer,
|
|
1359
1659
|
{
|
|
1360
1660
|
provide: APP_INTERCEPTOR,
|
|
1361
1661
|
useClass: RenderInterceptor
|