@nestjs-ssr/react 0.2.1 → 0.2.2
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 +1 -1
- package/dist/cli/init.js +278 -107
- package/dist/cli/init.mjs +280 -109
- package/dist/client.d.mts +319 -0
- package/dist/client.d.ts +319 -0
- package/dist/client.js +207 -0
- package/dist/client.mjs +200 -0
- package/dist/{index-C5Knql-9.d.mts → index-CaGD266H.d.ts} +2 -104
- package/dist/{index-C5Knql-9.d.ts → index-Dq1yt0sX.d.mts} +2 -104
- package/dist/index.d.mts +9 -321
- package/dist/index.d.ts +9 -321
- package/dist/index.js +144 -91
- package/dist/index.mjs +115 -63
- package/dist/render/index.d.mts +2 -1
- package/dist/render/index.d.ts +2 -1
- package/dist/render/index.js +134 -81
- package/dist/render/index.mjs +115 -63
- package/dist/render-response.interface-Dc-Kwb09.d.mts +104 -0
- package/dist/render-response.interface-Dc-Kwb09.d.ts +104 -0
- package/dist/templates/entry-client.tsx +6 -3
- package/dist/templates/entry-server.tsx +33 -2
- package/dist/templates/index.html +15 -12
- package/package.json +13 -1
- package/src/templates/entry-client.tsx +6 -3
- package/src/templates/entry-server.tsx +33 -2
- package/src/templates/index.html +15 -12
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { join, relative } from 'path';
|
|
|
5
5
|
import { uneval } from 'devalue';
|
|
6
6
|
import escapeHtml from 'escape-html';
|
|
7
7
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
8
|
-
import { createElement, createContext, useContext } from 'react';
|
|
8
|
+
import React, { createElement, createContext, useContext } from 'react';
|
|
9
9
|
import { switchMap } from 'rxjs/operators';
|
|
10
10
|
|
|
11
11
|
var __defProp = Object.defineProperty;
|
|
@@ -185,8 +185,6 @@ window.__LAYOUTS__ = ${uneval(layoutMetadata)};
|
|
|
185
185
|
TemplateParserService = _ts_decorate([
|
|
186
186
|
Injectable()
|
|
187
187
|
], TemplateParserService);
|
|
188
|
-
|
|
189
|
-
// src/render/error-pages/error-page-development.tsx
|
|
190
188
|
function ErrorPageDevelopment({ error, viewPath, phase }) {
|
|
191
189
|
const stackLines = error.stack ? error.stack.split("\n").slice(1) : [];
|
|
192
190
|
return /* @__PURE__ */ React.createElement("html", {
|
|
@@ -260,8 +258,6 @@ function ErrorPageDevelopment({ error, viewPath, phase }) {
|
|
|
260
258
|
}, /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "View Path:"), " ", viewPath), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "Error Phase:"), " ", phase === "shell" ? "Shell (before streaming started)" : "Streaming (during content delivery)"), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "Environment:"), " Development")))));
|
|
261
259
|
}
|
|
262
260
|
__name(ErrorPageDevelopment, "ErrorPageDevelopment");
|
|
263
|
-
|
|
264
|
-
// src/render/error-pages/error-page-production.tsx
|
|
265
261
|
function ErrorPageProduction() {
|
|
266
262
|
return /* @__PURE__ */ React.createElement("html", {
|
|
267
263
|
lang: "en"
|
|
@@ -671,60 +667,79 @@ var RenderService = class _RenderService {
|
|
|
671
667
|
async renderToStream(viewComponent, data = {}, res, head) {
|
|
672
668
|
const startTime = Date.now();
|
|
673
669
|
let shellReadyTime = 0;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
690
|
} else {
|
|
691
691
|
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
692
692
|
}
|
|
693
|
-
} else {
|
|
694
|
-
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
695
693
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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;
|
|
718
742
|
}
|
|
719
|
-
}, "onShellReady"),
|
|
720
|
-
onShellError: /* @__PURE__ */ __name((error) => {
|
|
721
|
-
this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
|
|
722
|
-
}, "onShellError"),
|
|
723
|
-
onError: /* @__PURE__ */ __name((error) => {
|
|
724
|
-
didError = true;
|
|
725
|
-
this.streamingErrorHandler.handleStreamError(error, componentName);
|
|
726
|
-
}, "onError"),
|
|
727
|
-
onAllReady: /* @__PURE__ */ __name(() => {
|
|
728
743
|
res.write(inlineScripts);
|
|
729
744
|
res.write(clientScript);
|
|
730
745
|
res.write(templateParts.rootEnd);
|
|
@@ -733,17 +748,25 @@ var RenderService = class _RenderService {
|
|
|
733
748
|
if (this.isDevelopment) {
|
|
734
749
|
const totalTime = Date.now() - startTime;
|
|
735
750
|
const streamTime = Date.now() - shellReadyTime;
|
|
736
|
-
|
|
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}`);
|
|
737
753
|
}
|
|
738
|
-
|
|
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();
|
|
739
768
|
});
|
|
740
|
-
|
|
741
|
-
abort();
|
|
742
|
-
});
|
|
743
|
-
} catch (error) {
|
|
744
|
-
const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
|
|
745
|
-
this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
|
|
746
|
-
}
|
|
769
|
+
});
|
|
747
770
|
}
|
|
748
771
|
};
|
|
749
772
|
RenderService = _ts_decorate3([
|
|
@@ -877,6 +900,9 @@ var RenderInterceptor = class {
|
|
|
877
900
|
const httpContext = context.switchToHttp();
|
|
878
901
|
const request = httpContext.getRequest();
|
|
879
902
|
const response = httpContext.getResponse();
|
|
903
|
+
if (typeof data === "string") {
|
|
904
|
+
return data;
|
|
905
|
+
}
|
|
880
906
|
const renderContext = {
|
|
881
907
|
url: request.url,
|
|
882
908
|
path: request.path,
|
|
@@ -967,11 +993,23 @@ var ViteInitializerService = class _ViteInitializerService {
|
|
|
967
993
|
viteMode;
|
|
968
994
|
vitePort;
|
|
969
995
|
viteServer = null;
|
|
996
|
+
isShuttingDown = false;
|
|
970
997
|
constructor(renderService, httpAdapterHost, viteConfig) {
|
|
971
998
|
this.renderService = renderService;
|
|
972
999
|
this.httpAdapterHost = httpAdapterHost;
|
|
973
1000
|
this.viteMode = viteConfig?.mode || "embedded";
|
|
974
1001
|
this.vitePort = viteConfig?.port || 5173;
|
|
1002
|
+
this.registerSignalHandlers();
|
|
1003
|
+
}
|
|
1004
|
+
registerSignalHandlers() {
|
|
1005
|
+
const cleanup = /* @__PURE__ */ __name(async (signal) => {
|
|
1006
|
+
if (this.isShuttingDown) return;
|
|
1007
|
+
this.isShuttingDown = true;
|
|
1008
|
+
this.logger.log(`Received ${signal}, closing Vite server...`);
|
|
1009
|
+
await this.closeViteServer();
|
|
1010
|
+
}, "cleanup");
|
|
1011
|
+
process.once("SIGTERM", () => cleanup("SIGTERM"));
|
|
1012
|
+
process.once("SIGINT", () => cleanup("SIGINT"));
|
|
975
1013
|
}
|
|
976
1014
|
async onModuleInit() {
|
|
977
1015
|
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
@@ -1060,9 +1098,23 @@ var ViteInitializerService = class _ViteInitializerService {
|
|
|
1060
1098
|
* This prevents port conflicts on hot reload
|
|
1061
1099
|
*/
|
|
1062
1100
|
async onModuleDestroy() {
|
|
1101
|
+
await this.closeViteServer();
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Cleanup: Close Vite server on application shutdown
|
|
1105
|
+
* Belt-and-suspenders approach with onModuleDestroy
|
|
1106
|
+
*/
|
|
1107
|
+
async onApplicationShutdown() {
|
|
1108
|
+
await this.closeViteServer();
|
|
1109
|
+
}
|
|
1110
|
+
async closeViteServer() {
|
|
1111
|
+
if (this.isShuttingDown && !this.viteServer) return;
|
|
1112
|
+
this.isShuttingDown = true;
|
|
1063
1113
|
if (this.viteServer) {
|
|
1064
1114
|
try {
|
|
1115
|
+
this.renderService.setViteServer(null);
|
|
1065
1116
|
await this.viteServer.close();
|
|
1117
|
+
this.viteServer = null;
|
|
1066
1118
|
this.logger.log("\u2713 Vite server closed");
|
|
1067
1119
|
} catch (error) {
|
|
1068
1120
|
this.logger.warn(`Failed to close Vite server: ${error.message}`);
|
package/dist/render/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { E as ErrorPageDevelopment,
|
|
1
|
+
export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-Dq1yt0sX.mjs';
|
|
2
2
|
import '@nestjs/common';
|
|
3
3
|
import 'react';
|
|
4
|
+
import '../render-response.interface-Dc-Kwb09.mjs';
|
|
4
5
|
import 'vite';
|
|
5
6
|
import 'express';
|
|
6
7
|
import '@nestjs/core';
|
package/dist/render/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { E as ErrorPageDevelopment,
|
|
1
|
+
export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-CaGD266H.js';
|
|
2
2
|
import '@nestjs/common';
|
|
3
3
|
import 'react';
|
|
4
|
+
import '../render-response.interface-Dc-Kwb09.js';
|
|
4
5
|
import 'vite';
|
|
5
6
|
import 'express';
|
|
6
7
|
import '@nestjs/core';
|
package/dist/render/index.js
CHANGED
|
@@ -7,12 +7,13 @@ var path = require('path');
|
|
|
7
7
|
var devalue = require('devalue');
|
|
8
8
|
var escapeHtml = require('escape-html');
|
|
9
9
|
var server = require('react-dom/server');
|
|
10
|
-
var
|
|
10
|
+
var React = require('react');
|
|
11
11
|
var operators = require('rxjs/operators');
|
|
12
12
|
|
|
13
13
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
14
|
|
|
15
15
|
var escapeHtml__default = /*#__PURE__*/_interopDefault(escapeHtml);
|
|
16
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
16
17
|
|
|
17
18
|
var __defProp = Object.defineProperty;
|
|
18
19
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
@@ -191,18 +192,16 @@ window.__LAYOUTS__ = ${devalue.uneval(layoutMetadata)};
|
|
|
191
192
|
exports.TemplateParserService = _ts_decorate([
|
|
192
193
|
common.Injectable()
|
|
193
194
|
], exports.TemplateParserService);
|
|
194
|
-
|
|
195
|
-
// src/render/error-pages/error-page-development.tsx
|
|
196
195
|
function ErrorPageDevelopment({ error, viewPath, phase }) {
|
|
197
196
|
const stackLines = error.stack ? error.stack.split("\n").slice(1) : [];
|
|
198
|
-
return /* @__PURE__ */
|
|
197
|
+
return /* @__PURE__ */ React__default.default.createElement("html", {
|
|
199
198
|
lang: "en"
|
|
200
|
-
}, /* @__PURE__ */
|
|
199
|
+
}, /* @__PURE__ */ React__default.default.createElement("head", null, /* @__PURE__ */ React__default.default.createElement("meta", {
|
|
201
200
|
charSet: "UTF-8"
|
|
202
|
-
}), /* @__PURE__ */
|
|
201
|
+
}), /* @__PURE__ */ React__default.default.createElement("meta", {
|
|
203
202
|
name: "viewport",
|
|
204
203
|
content: "width=device-width, initial-scale=1.0"
|
|
205
|
-
}), /* @__PURE__ */
|
|
204
|
+
}), /* @__PURE__ */ React__default.default.createElement("title", null, `SSR Error - ${error.name}`), /* @__PURE__ */ React__default.default.createElement("style", {
|
|
206
205
|
dangerouslySetInnerHTML: {
|
|
207
206
|
__html: `
|
|
208
207
|
body {
|
|
@@ -253,30 +252,28 @@ function ErrorPageDevelopment({ error, viewPath, phase }) {
|
|
|
253
252
|
}
|
|
254
253
|
`
|
|
255
254
|
}
|
|
256
|
-
})), /* @__PURE__ */
|
|
255
|
+
})), /* @__PURE__ */ React__default.default.createElement("body", null, /* @__PURE__ */ React__default.default.createElement("div", {
|
|
257
256
|
className: "error-container"
|
|
258
|
-
}, /* @__PURE__ */
|
|
257
|
+
}, /* @__PURE__ */ React__default.default.createElement("h1", null, "Server-Side Rendering Error"), /* @__PURE__ */ React__default.default.createElement("div", {
|
|
259
258
|
className: "error-type"
|
|
260
|
-
}, error.name), /* @__PURE__ */
|
|
259
|
+
}, error.name), /* @__PURE__ */ React__default.default.createElement("div", {
|
|
261
260
|
className: "error-message"
|
|
262
|
-
}, error.message), /* @__PURE__ */
|
|
261
|
+
}, error.message), /* @__PURE__ */ React__default.default.createElement("h2", null, "Stack Trace"), /* @__PURE__ */ React__default.default.createElement("div", {
|
|
263
262
|
className: "stack-trace"
|
|
264
|
-
}, /* @__PURE__ */
|
|
263
|
+
}, /* @__PURE__ */ React__default.default.createElement("pre", null, stackLines.join("\n"))), /* @__PURE__ */ React__default.default.createElement("div", {
|
|
265
264
|
className: "meta"
|
|
266
|
-
}, /* @__PURE__ */
|
|
265
|
+
}, /* @__PURE__ */ React__default.default.createElement("p", null, /* @__PURE__ */ React__default.default.createElement("strong", null, "View Path:"), " ", viewPath), /* @__PURE__ */ React__default.default.createElement("p", null, /* @__PURE__ */ React__default.default.createElement("strong", null, "Error Phase:"), " ", phase === "shell" ? "Shell (before streaming started)" : "Streaming (during content delivery)"), /* @__PURE__ */ React__default.default.createElement("p", null, /* @__PURE__ */ React__default.default.createElement("strong", null, "Environment:"), " Development")))));
|
|
267
266
|
}
|
|
268
267
|
__name(ErrorPageDevelopment, "ErrorPageDevelopment");
|
|
269
|
-
|
|
270
|
-
// src/render/error-pages/error-page-production.tsx
|
|
271
268
|
function ErrorPageProduction() {
|
|
272
|
-
return /* @__PURE__ */
|
|
269
|
+
return /* @__PURE__ */ React__default.default.createElement("html", {
|
|
273
270
|
lang: "en"
|
|
274
|
-
}, /* @__PURE__ */
|
|
271
|
+
}, /* @__PURE__ */ React__default.default.createElement("head", null, /* @__PURE__ */ React__default.default.createElement("meta", {
|
|
275
272
|
charSet: "UTF-8"
|
|
276
|
-
}), /* @__PURE__ */
|
|
273
|
+
}), /* @__PURE__ */ React__default.default.createElement("meta", {
|
|
277
274
|
name: "viewport",
|
|
278
275
|
content: "width=device-width, initial-scale=1.0"
|
|
279
|
-
}), /* @__PURE__ */
|
|
276
|
+
}), /* @__PURE__ */ React__default.default.createElement("title", null, "Error"), /* @__PURE__ */ React__default.default.createElement("style", {
|
|
280
277
|
dangerouslySetInnerHTML: {
|
|
281
278
|
__html: `
|
|
282
279
|
body {
|
|
@@ -303,9 +300,9 @@ function ErrorPageProduction() {
|
|
|
303
300
|
}
|
|
304
301
|
`
|
|
305
302
|
}
|
|
306
|
-
})), /* @__PURE__ */
|
|
303
|
+
})), /* @__PURE__ */ React__default.default.createElement("body", null, /* @__PURE__ */ React__default.default.createElement("div", {
|
|
307
304
|
className: "error-container"
|
|
308
|
-
}, /* @__PURE__ */
|
|
305
|
+
}, /* @__PURE__ */ React__default.default.createElement("h1", null, "500"), /* @__PURE__ */ React__default.default.createElement("p", null, "Internal Server Error"), /* @__PURE__ */ React__default.default.createElement("p", null, "Something went wrong while rendering this page."))));
|
|
309
306
|
}
|
|
310
307
|
__name(ErrorPageProduction, "ErrorPageProduction");
|
|
311
308
|
|
|
@@ -364,7 +361,7 @@ exports.StreamingErrorHandler = class _StreamingErrorHandler {
|
|
|
364
361
|
*/
|
|
365
362
|
renderDevelopmentErrorPage(error, viewPath, phase) {
|
|
366
363
|
const ErrorComponent = this.errorPageDevelopment || ErrorPageDevelopment;
|
|
367
|
-
const element =
|
|
364
|
+
const element = React.createElement(ErrorComponent, {
|
|
368
365
|
error,
|
|
369
366
|
viewPath,
|
|
370
367
|
phase
|
|
@@ -376,7 +373,7 @@ exports.StreamingErrorHandler = class _StreamingErrorHandler {
|
|
|
376
373
|
*/
|
|
377
374
|
renderProductionErrorPage() {
|
|
378
375
|
const ErrorComponent = this.errorPageProduction || ErrorPageProduction;
|
|
379
|
-
const element =
|
|
376
|
+
const element = React.createElement(ErrorComponent);
|
|
380
377
|
return "<!DOCTYPE html>\n" + server.renderToStaticMarkup(element);
|
|
381
378
|
}
|
|
382
379
|
};
|
|
@@ -677,60 +674,79 @@ exports.RenderService = class _RenderService {
|
|
|
677
674
|
async renderToStream(viewComponent, data = {}, res, head) {
|
|
678
675
|
const startTime = Date.now();
|
|
679
676
|
let shellReadyTime = 0;
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
677
|
+
return new Promise((resolve, reject) => {
|
|
678
|
+
const executeStream = /* @__PURE__ */ __name(async () => {
|
|
679
|
+
let template = this.template;
|
|
680
|
+
if (this.vite) {
|
|
681
|
+
template = await this.vite.transformIndexHtml("/", template);
|
|
682
|
+
}
|
|
683
|
+
const templateParts = this.templateParser.parseTemplate(template);
|
|
684
|
+
let renderModule;
|
|
685
|
+
if (this.vite) {
|
|
686
|
+
renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
|
|
687
|
+
} else {
|
|
688
|
+
if (this.serverManifest) {
|
|
689
|
+
const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
|
|
690
|
+
if (manifestEntry) {
|
|
691
|
+
const [, entry] = manifestEntry;
|
|
692
|
+
const serverPath = path.join(process.cwd(), "dist/server", entry.file);
|
|
693
|
+
renderModule = await import(serverPath);
|
|
694
|
+
} else {
|
|
695
|
+
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
696
|
+
}
|
|
696
697
|
} else {
|
|
697
698
|
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
698
699
|
}
|
|
699
|
-
} else {
|
|
700
|
-
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
701
700
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
701
|
+
const { data: pageData, __context: context, __layouts: layouts } = data;
|
|
702
|
+
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
703
|
+
const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName, layouts);
|
|
704
|
+
const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
|
|
705
|
+
const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
|
|
706
|
+
const headTags = this.templateParser.buildHeadTags(head);
|
|
707
|
+
let didError = false;
|
|
708
|
+
let shellErrorOccurred = false;
|
|
709
|
+
const { PassThrough } = await import('stream');
|
|
710
|
+
const reactStream = new PassThrough();
|
|
711
|
+
let allReadyFired = false;
|
|
712
|
+
const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
|
|
713
|
+
onShellReady: /* @__PURE__ */ __name(() => {
|
|
714
|
+
shellReadyTime = Date.now();
|
|
715
|
+
if (!res.headersSent) {
|
|
716
|
+
res.statusCode = didError ? 500 : 200;
|
|
717
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
718
|
+
}
|
|
719
|
+
let htmlStart = templateParts.htmlStart;
|
|
720
|
+
htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
|
|
721
|
+
htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
|
|
722
|
+
res.write(htmlStart);
|
|
723
|
+
res.write(templateParts.rootStart);
|
|
724
|
+
pipe(reactStream);
|
|
725
|
+
reactStream.pipe(res, {
|
|
726
|
+
end: false
|
|
727
|
+
});
|
|
728
|
+
if (this.isDevelopment) {
|
|
729
|
+
const ttfb = shellReadyTime - startTime;
|
|
730
|
+
this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
|
|
731
|
+
}
|
|
732
|
+
}, "onShellReady"),
|
|
733
|
+
onShellError: /* @__PURE__ */ __name((error) => {
|
|
734
|
+
shellErrorOccurred = true;
|
|
735
|
+
this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
|
|
736
|
+
resolve();
|
|
737
|
+
}, "onShellError"),
|
|
738
|
+
onError: /* @__PURE__ */ __name((error) => {
|
|
739
|
+
didError = true;
|
|
740
|
+
this.streamingErrorHandler.handleStreamError(error, componentName);
|
|
741
|
+
}, "onError"),
|
|
742
|
+
onAllReady: /* @__PURE__ */ __name(() => {
|
|
743
|
+
allReadyFired = true;
|
|
744
|
+
}, "onAllReady")
|
|
745
|
+
});
|
|
746
|
+
reactStream.on("end", () => {
|
|
747
|
+
if (shellErrorOccurred) {
|
|
748
|
+
return;
|
|
724
749
|
}
|
|
725
|
-
}, "onShellReady"),
|
|
726
|
-
onShellError: /* @__PURE__ */ __name((error) => {
|
|
727
|
-
this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
|
|
728
|
-
}, "onShellError"),
|
|
729
|
-
onError: /* @__PURE__ */ __name((error) => {
|
|
730
|
-
didError = true;
|
|
731
|
-
this.streamingErrorHandler.handleStreamError(error, componentName);
|
|
732
|
-
}, "onError"),
|
|
733
|
-
onAllReady: /* @__PURE__ */ __name(() => {
|
|
734
750
|
res.write(inlineScripts);
|
|
735
751
|
res.write(clientScript);
|
|
736
752
|
res.write(templateParts.rootEnd);
|
|
@@ -739,17 +755,25 @@ exports.RenderService = class _RenderService {
|
|
|
739
755
|
if (this.isDevelopment) {
|
|
740
756
|
const totalTime = Date.now() - startTime;
|
|
741
757
|
const streamTime = Date.now() - shellReadyTime;
|
|
742
|
-
|
|
758
|
+
const viaAllReady = allReadyFired ? " (onAllReady fired)" : " (onAllReady never fired)";
|
|
759
|
+
this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)${viaAllReady}`);
|
|
743
760
|
}
|
|
744
|
-
|
|
761
|
+
resolve();
|
|
762
|
+
});
|
|
763
|
+
reactStream.on("error", (error) => {
|
|
764
|
+
reject(error);
|
|
765
|
+
});
|
|
766
|
+
res.on("close", () => {
|
|
767
|
+
abort();
|
|
768
|
+
resolve();
|
|
769
|
+
});
|
|
770
|
+
}, "executeStream");
|
|
771
|
+
executeStream().catch((error) => {
|
|
772
|
+
const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
|
|
773
|
+
this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
|
|
774
|
+
resolve();
|
|
745
775
|
});
|
|
746
|
-
|
|
747
|
-
abort();
|
|
748
|
-
});
|
|
749
|
-
} catch (error) {
|
|
750
|
-
const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
|
|
751
|
-
this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
|
|
752
|
-
}
|
|
776
|
+
});
|
|
753
777
|
}
|
|
754
778
|
};
|
|
755
779
|
exports.RenderService = _ts_decorate3([
|
|
@@ -865,6 +889,9 @@ exports.RenderInterceptor = class RenderInterceptor {
|
|
|
865
889
|
const httpContext = context.switchToHttp();
|
|
866
890
|
const request = httpContext.getRequest();
|
|
867
891
|
const response = httpContext.getResponse();
|
|
892
|
+
if (typeof data === "string") {
|
|
893
|
+
return data;
|
|
894
|
+
}
|
|
868
895
|
const renderContext = {
|
|
869
896
|
url: request.url,
|
|
870
897
|
path: request.path,
|
|
@@ -955,11 +982,23 @@ var ViteInitializerService = class _ViteInitializerService {
|
|
|
955
982
|
viteMode;
|
|
956
983
|
vitePort;
|
|
957
984
|
viteServer = null;
|
|
985
|
+
isShuttingDown = false;
|
|
958
986
|
constructor(renderService, httpAdapterHost, viteConfig) {
|
|
959
987
|
this.renderService = renderService;
|
|
960
988
|
this.httpAdapterHost = httpAdapterHost;
|
|
961
989
|
this.viteMode = viteConfig?.mode || "embedded";
|
|
962
990
|
this.vitePort = viteConfig?.port || 5173;
|
|
991
|
+
this.registerSignalHandlers();
|
|
992
|
+
}
|
|
993
|
+
registerSignalHandlers() {
|
|
994
|
+
const cleanup = /* @__PURE__ */ __name(async (signal) => {
|
|
995
|
+
if (this.isShuttingDown) return;
|
|
996
|
+
this.isShuttingDown = true;
|
|
997
|
+
this.logger.log(`Received ${signal}, closing Vite server...`);
|
|
998
|
+
await this.closeViteServer();
|
|
999
|
+
}, "cleanup");
|
|
1000
|
+
process.once("SIGTERM", () => cleanup("SIGTERM"));
|
|
1001
|
+
process.once("SIGINT", () => cleanup("SIGINT"));
|
|
963
1002
|
}
|
|
964
1003
|
async onModuleInit() {
|
|
965
1004
|
const isDevelopment = process.env.NODE_ENV !== "production";
|
|
@@ -1048,9 +1087,23 @@ var ViteInitializerService = class _ViteInitializerService {
|
|
|
1048
1087
|
* This prevents port conflicts on hot reload
|
|
1049
1088
|
*/
|
|
1050
1089
|
async onModuleDestroy() {
|
|
1090
|
+
await this.closeViteServer();
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Cleanup: Close Vite server on application shutdown
|
|
1094
|
+
* Belt-and-suspenders approach with onModuleDestroy
|
|
1095
|
+
*/
|
|
1096
|
+
async onApplicationShutdown() {
|
|
1097
|
+
await this.closeViteServer();
|
|
1098
|
+
}
|
|
1099
|
+
async closeViteServer() {
|
|
1100
|
+
if (this.isShuttingDown && !this.viteServer) return;
|
|
1101
|
+
this.isShuttingDown = true;
|
|
1051
1102
|
if (this.viteServer) {
|
|
1052
1103
|
try {
|
|
1104
|
+
this.renderService.setViteServer(null);
|
|
1053
1105
|
await this.viteServer.close();
|
|
1106
|
+
this.viteServer = null;
|
|
1054
1107
|
this.logger.log("\u2713 Vite server closed");
|
|
1055
1108
|
} catch (error) {
|
|
1056
1109
|
this.logger.warn(`Failed to close Vite server: ${error.message}`);
|