@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/dist/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 react = require('react');
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__ */ React.createElement("html", {
197
+ return /* @__PURE__ */ React__default.default.createElement("html", {
199
198
  lang: "en"
200
- }, /* @__PURE__ */ React.createElement("head", null, /* @__PURE__ */ React.createElement("meta", {
199
+ }, /* @__PURE__ */ React__default.default.createElement("head", null, /* @__PURE__ */ React__default.default.createElement("meta", {
201
200
  charSet: "UTF-8"
202
- }), /* @__PURE__ */ React.createElement("meta", {
201
+ }), /* @__PURE__ */ React__default.default.createElement("meta", {
203
202
  name: "viewport",
204
203
  content: "width=device-width, initial-scale=1.0"
205
- }), /* @__PURE__ */ React.createElement("title", null, `SSR Error - ${error.name}`), /* @__PURE__ */ React.createElement("style", {
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__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement("div", {
255
+ })), /* @__PURE__ */ React__default.default.createElement("body", null, /* @__PURE__ */ React__default.default.createElement("div", {
257
256
  className: "error-container"
258
- }, /* @__PURE__ */ React.createElement("h1", null, "Server-Side Rendering Error"), /* @__PURE__ */ React.createElement("div", {
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__ */ React.createElement("div", {
259
+ }, error.name), /* @__PURE__ */ React__default.default.createElement("div", {
261
260
  className: "error-message"
262
- }, error.message), /* @__PURE__ */ React.createElement("h2", null, "Stack Trace"), /* @__PURE__ */ React.createElement("div", {
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__ */ React.createElement("pre", null, stackLines.join("\n"))), /* @__PURE__ */ React.createElement("div", {
263
+ }, /* @__PURE__ */ React__default.default.createElement("pre", null, stackLines.join("\n"))), /* @__PURE__ */ React__default.default.createElement("div", {
265
264
  className: "meta"
266
- }, /* @__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")))));
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__ */ React.createElement("html", {
269
+ return /* @__PURE__ */ React__default.default.createElement("html", {
273
270
  lang: "en"
274
- }, /* @__PURE__ */ React.createElement("head", null, /* @__PURE__ */ React.createElement("meta", {
271
+ }, /* @__PURE__ */ React__default.default.createElement("head", null, /* @__PURE__ */ React__default.default.createElement("meta", {
275
272
  charSet: "UTF-8"
276
- }), /* @__PURE__ */ React.createElement("meta", {
273
+ }), /* @__PURE__ */ React__default.default.createElement("meta", {
277
274
  name: "viewport",
278
275
  content: "width=device-width, initial-scale=1.0"
279
- }), /* @__PURE__ */ React.createElement("title", null, "Error"), /* @__PURE__ */ React.createElement("style", {
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__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement("div", {
303
+ })), /* @__PURE__ */ React__default.default.createElement("body", null, /* @__PURE__ */ React__default.default.createElement("div", {
307
304
  className: "error-container"
308
- }, /* @__PURE__ */ React.createElement("h1", null, "500"), /* @__PURE__ */ React.createElement("p", null, "Internal Server Error"), /* @__PURE__ */ React.createElement("p", null, "Something went wrong while rendering this page."))));
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 = react.createElement(ErrorComponent, {
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 = react.createElement(ErrorComponent);
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
- try {
681
- let template = this.template;
682
- if (this.vite) {
683
- template = await this.vite.transformIndexHtml("/", template);
684
- }
685
- const templateParts = this.templateParser.parseTemplate(template);
686
- let renderModule;
687
- if (this.vite) {
688
- renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
689
- } else {
690
- if (this.serverManifest) {
691
- const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
692
- if (manifestEntry) {
693
- const [, entry] = manifestEntry;
694
- const serverPath = path.join(process.cwd(), "dist/server", entry.file);
695
- renderModule = await import(serverPath);
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
- const { data: pageData, __context: context, __layouts: layouts } = data;
704
- const componentName = viewComponent.displayName || viewComponent.name || "Component";
705
- const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName, layouts);
706
- const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
707
- const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
708
- const headTags = this.templateParser.buildHeadTags(head);
709
- let didError = false;
710
- const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
711
- onShellReady: /* @__PURE__ */ __name(() => {
712
- shellReadyTime = Date.now();
713
- res.statusCode = didError ? 500 : 200;
714
- res.setHeader("Content-Type", "text/html; charset=utf-8");
715
- let htmlStart = templateParts.htmlStart;
716
- htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
717
- htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
718
- res.write(htmlStart);
719
- res.write(templateParts.rootStart);
720
- pipe(res);
721
- if (this.isDevelopment) {
722
- const ttfb = shellReadyTime - startTime;
723
- this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
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
- this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)`);
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
- }, "onAllReady")
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
- res.on("close", () => {
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([
@@ -883,6 +907,9 @@ exports.RenderInterceptor = class RenderInterceptor {
883
907
  const httpContext = context.switchToHttp();
884
908
  const request = httpContext.getRequest();
885
909
  const response = httpContext.getResponse();
910
+ if (typeof data === "string") {
911
+ return data;
912
+ }
886
913
  const renderContext = {
887
914
  url: request.url,
888
915
  path: request.path,
@@ -973,11 +1000,23 @@ var ViteInitializerService = class _ViteInitializerService {
973
1000
  viteMode;
974
1001
  vitePort;
975
1002
  viteServer = null;
1003
+ isShuttingDown = false;
976
1004
  constructor(renderService, httpAdapterHost, viteConfig) {
977
1005
  this.renderService = renderService;
978
1006
  this.httpAdapterHost = httpAdapterHost;
979
1007
  this.viteMode = viteConfig?.mode || "embedded";
980
1008
  this.vitePort = viteConfig?.port || 5173;
1009
+ this.registerSignalHandlers();
1010
+ }
1011
+ registerSignalHandlers() {
1012
+ const cleanup = /* @__PURE__ */ __name(async (signal) => {
1013
+ if (this.isShuttingDown) return;
1014
+ this.isShuttingDown = true;
1015
+ this.logger.log(`Received ${signal}, closing Vite server...`);
1016
+ await this.closeViteServer();
1017
+ }, "cleanup");
1018
+ process.once("SIGTERM", () => cleanup("SIGTERM"));
1019
+ process.once("SIGINT", () => cleanup("SIGINT"));
981
1020
  }
982
1021
  async onModuleInit() {
983
1022
  const isDevelopment = process.env.NODE_ENV !== "production";
@@ -1066,9 +1105,23 @@ var ViteInitializerService = class _ViteInitializerService {
1066
1105
  * This prevents port conflicts on hot reload
1067
1106
  */
1068
1107
  async onModuleDestroy() {
1108
+ await this.closeViteServer();
1109
+ }
1110
+ /**
1111
+ * Cleanup: Close Vite server on application shutdown
1112
+ * Belt-and-suspenders approach with onModuleDestroy
1113
+ */
1114
+ async onApplicationShutdown() {
1115
+ await this.closeViteServer();
1116
+ }
1117
+ async closeViteServer() {
1118
+ if (this.isShuttingDown && !this.viteServer) return;
1119
+ this.isShuttingDown = true;
1069
1120
  if (this.viteServer) {
1070
1121
  try {
1122
+ this.renderService.setViteServer(null);
1071
1123
  await this.viteServer.close();
1124
+ this.viteServer = null;
1072
1125
  this.logger.log("\u2713 Vite server closed");
1073
1126
  } catch (error) {
1074
1127
  this.logger.warn(`Failed to close Vite server: ${error.message}`);
@@ -1337,9 +1390,9 @@ exports.RenderModule = _ts_decorate6([
1337
1390
  ]
1338
1391
  })
1339
1392
  ], exports.RenderModule);
1340
- var PageContext = /* @__PURE__ */ react.createContext(null);
1393
+ var PageContext = /* @__PURE__ */ React.createContext(null);
1341
1394
  function PageContextProvider({ context, children }) {
1342
- return /* @__PURE__ */ React.createElement(PageContext.Provider, {
1395
+ return /* @__PURE__ */ React__default.default.createElement(PageContext.Provider, {
1343
1396
  value: context
1344
1397
  }, children);
1345
1398
  }
@@ -1351,7 +1404,7 @@ function createSSRHooks() {
1351
1404
  * Contains URL metadata, headers, and any custom properties you've added.
1352
1405
  */
1353
1406
  usePageContext: /* @__PURE__ */ __name(() => {
1354
- const context = react.useContext(PageContext);
1407
+ const context = React.useContext(PageContext);
1355
1408
  if (!context) {
1356
1409
  throw new Error("usePageContext must be used within PageContextProvider");
1357
1410
  }
@@ -1368,7 +1421,7 @@ function createSSRHooks() {
1368
1421
  * ```
1369
1422
  */
1370
1423
  useParams: /* @__PURE__ */ __name(() => {
1371
- const context = react.useContext(PageContext);
1424
+ const context = React.useContext(PageContext);
1372
1425
  if (!context) {
1373
1426
  throw new Error("useParams must be used within PageContextProvider");
1374
1427
  }
@@ -1386,7 +1439,7 @@ function createSSRHooks() {
1386
1439
  * ```
1387
1440
  */
1388
1441
  useQuery: /* @__PURE__ */ __name(() => {
1389
- const context = react.useContext(PageContext);
1442
+ const context = React.useContext(PageContext);
1390
1443
  if (!context) {
1391
1444
  throw new Error("useQuery must be used within PageContextProvider");
1392
1445
  }
@@ -1406,7 +1459,7 @@ function createSSRHooks() {
1406
1459
  * ```
1407
1460
  */
1408
1461
  useRequest: /* @__PURE__ */ __name(() => {
1409
- const context = react.useContext(PageContext);
1462
+ const context = React.useContext(PageContext);
1410
1463
  if (!context) {
1411
1464
  throw new Error("useRequest must be used within PageContextProvider");
1412
1465
  }
@@ -1432,7 +1485,7 @@ function createSSRHooks() {
1432
1485
  * ```
1433
1486
  */
1434
1487
  useHeaders: /* @__PURE__ */ __name(() => {
1435
- const context = react.useContext(PageContext);
1488
+ const context = React.useContext(PageContext);
1436
1489
  if (!context) {
1437
1490
  throw new Error("useHeaders must be used within PageContextProvider");
1438
1491
  }
@@ -1467,7 +1520,7 @@ function createSSRHooks() {
1467
1520
  * ```
1468
1521
  */
1469
1522
  useHeader: /* @__PURE__ */ __name((name) => {
1470
- const context = react.useContext(PageContext);
1523
+ const context = React.useContext(PageContext);
1471
1524
  if (!context) {
1472
1525
  throw new Error("useHeader must be used within PageContextProvider");
1473
1526
  }
@@ -1493,7 +1546,7 @@ function createSSRHooks() {
1493
1546
  * ```
1494
1547
  */
1495
1548
  useCookies: /* @__PURE__ */ __name(() => {
1496
- const context = react.useContext(PageContext);
1549
+ const context = React.useContext(PageContext);
1497
1550
  if (!context) {
1498
1551
  throw new Error("useCookies must be used within PageContextProvider");
1499
1552
  }
@@ -1515,7 +1568,7 @@ function createSSRHooks() {
1515
1568
  * ```
1516
1569
  */
1517
1570
  useCookie: /* @__PURE__ */ __name((name) => {
1518
- const context = react.useContext(PageContext);
1571
+ const context = React.useContext(PageContext);
1519
1572
  if (!context) {
1520
1573
  throw new Error("useCookie must be used within PageContextProvider");
1521
1574
  }