@nestjs-ssr/react 0.1.5 → 0.1.6

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.
@@ -1,7 +1,7 @@
1
1
  import { Injectable, Logger, Optional, Inject, Global, Module } from '@nestjs/common';
2
2
  import { HttpAdapterHost, APP_INTERCEPTOR, Reflector } from '@nestjs/core';
3
3
  import { existsSync, readFileSync } from 'fs';
4
- import { join } from 'path';
4
+ import { join, relative } from 'path';
5
5
  import serialize from 'serialize-javascript';
6
6
  import escapeHtml from 'escape-html';
7
7
  import { renderToStaticMarkup } from 'react-dom/server';
@@ -102,7 +102,7 @@ var TemplateParserService = class {
102
102
  * This library handles all edge cases including escaping dangerous characters,
103
103
  * functions, dates, regexes, and prevents prototype pollution.
104
104
  */
105
- buildInlineScripts(data, context, componentPath) {
105
+ buildInlineScripts(data, context, componentName) {
106
106
  return `<script>
107
107
  window.__INITIAL_STATE__ = ${serialize(data, {
108
108
  isJSON: true
@@ -110,7 +110,7 @@ window.__INITIAL_STATE__ = ${serialize(data, {
110
110
  window.__CONTEXT__ = ${serialize(context, {
111
111
  isJSON: true
112
112
  })};
113
- window.__COMPONENT_PATH__ = ${serialize(componentPath, {
113
+ window.__COMPONENT_NAME__ = ${serialize(componentName, {
114
114
  isJSON: true
115
115
  })};
116
116
  </script>`;
@@ -420,12 +420,28 @@ var RenderService = class _RenderService {
420
420
  serverManifest = null;
421
421
  isDevelopment;
422
422
  ssrMode;
423
+ entryServerPath;
424
+ entryClientPath;
423
425
  constructor(templateParser, streamingErrorHandler, ssrMode, defaultHead) {
424
426
  this.templateParser = templateParser;
425
427
  this.streamingErrorHandler = streamingErrorHandler;
426
428
  this.defaultHead = defaultHead;
427
429
  this.isDevelopment = process.env.NODE_ENV !== "production";
428
430
  this.ssrMode = ssrMode || process.env.SSR_MODE || "string";
431
+ const absoluteServerPath = join(__dirname, "/templates/entry-server.tsx");
432
+ const relativeServerPath = relative(process.cwd(), absoluteServerPath);
433
+ if (relativeServerPath.startsWith("..")) {
434
+ this.entryServerPath = absoluteServerPath;
435
+ } else {
436
+ this.entryServerPath = "/" + relativeServerPath.replace(/\\/g, "/");
437
+ }
438
+ const absoluteClientPath = join(__dirname, "/templates/entry-client.tsx");
439
+ const relativeClientPath = relative(process.cwd(), absoluteClientPath);
440
+ if (relativeClientPath.startsWith("..")) {
441
+ this.entryClientPath = absoluteClientPath;
442
+ } else {
443
+ this.entryClientPath = "/" + relativeClientPath.replace(/\\/g, "/");
444
+ }
429
445
  let templatePath;
430
446
  if (this.isDevelopment) {
431
447
  const packageTemplatePaths = [
@@ -477,15 +493,15 @@ var RenderService = class _RenderService {
477
493
  /**
478
494
  * Main render method that routes to string or stream mode
479
495
  */
480
- async render(viewPath, data = {}, res, head) {
496
+ async render(viewComponent, data = {}, res, head) {
481
497
  const mergedHead = this.mergeHead(this.defaultHead, head);
482
498
  if (this.ssrMode === "stream") {
483
499
  if (!res) {
484
500
  throw new Error("Response object is required for streaming SSR mode. Pass res as third parameter.");
485
501
  }
486
- return this.renderToStream(viewPath, data, res, mergedHead);
502
+ return this.renderToStream(viewComponent, data, res, mergedHead);
487
503
  }
488
- return this.renderToString(viewPath, data, mergedHead);
504
+ return this.renderToString(viewComponent, data, mergedHead);
489
505
  }
490
506
  /**
491
507
  * Merge default head with page-specific head
@@ -512,7 +528,7 @@ var RenderService = class _RenderService {
512
528
  /**
513
529
  * Traditional string-based SSR using renderToString
514
530
  */
515
- async renderToString(viewPath, data = {}, head) {
531
+ async renderToString(viewComponent, data = {}, head) {
516
532
  const startTime = Date.now();
517
533
  try {
518
534
  let template = this.template;
@@ -521,7 +537,7 @@ var RenderService = class _RenderService {
521
537
  }
522
538
  let renderModule;
523
539
  if (this.vite) {
524
- renderModule = await this.vite.ssrLoadModule("/src/entry-server.tsx");
540
+ renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
525
541
  } else {
526
542
  if (this.serverManifest) {
527
543
  const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
@@ -537,7 +553,8 @@ var RenderService = class _RenderService {
537
553
  }
538
554
  }
539
555
  const { data: pageData, __context: context } = data;
540
- const appHtml = await renderModule.renderComponent(viewPath, data);
556
+ const appHtml = await renderModule.renderComponent(viewComponent, data);
557
+ const componentName = viewComponent.displayName || viewComponent.name || "Component";
541
558
  const initialStateScript = `
542
559
  <script>
543
560
  window.__INITIAL_STATE__ = ${serialize(pageData, {
@@ -546,7 +563,7 @@ var RenderService = class _RenderService {
546
563
  window.__CONTEXT__ = ${serialize(context, {
547
564
  isJSON: true
548
565
  })};
549
- window.__COMPONENT_PATH__ = ${serialize(viewPath, {
566
+ window.__COMPONENT_NAME__ = ${serialize(componentName, {
550
567
  isJSON: true
551
568
  })};
552
569
  </script>
@@ -554,7 +571,7 @@ var RenderService = class _RenderService {
554
571
  let clientScript = "";
555
572
  let styles = "";
556
573
  if (this.vite) {
557
- clientScript = `<script type="module" src="/src/entry-client.tsx"></script>`;
574
+ clientScript = `<script type="module" src="${this.entryClientPath}"></script>`;
558
575
  styles = "";
559
576
  } else {
560
577
  if (this.manifest) {
@@ -584,7 +601,8 @@ var RenderService = class _RenderService {
584
601
  html = html.replace("<!--head-meta-->", headTags);
585
602
  if (this.isDevelopment) {
586
603
  const duration = Date.now() - startTime;
587
- this.logger.log(`[SSR] ${viewPath} rendered in ${duration}ms (string mode)`);
604
+ const componentName2 = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
605
+ this.logger.log(`[SSR] ${componentName2} rendered in ${duration}ms (string mode)`);
588
606
  }
589
607
  return html;
590
608
  } catch (error) {
@@ -594,7 +612,7 @@ var RenderService = class _RenderService {
594
612
  /**
595
613
  * Modern streaming SSR using renderToPipeableStream
596
614
  */
597
- async renderToStream(viewPath, data = {}, res, head) {
615
+ async renderToStream(viewComponent, data = {}, res, head) {
598
616
  const startTime = Date.now();
599
617
  let shellReadyTime = 0;
600
618
  try {
@@ -605,7 +623,7 @@ var RenderService = class _RenderService {
605
623
  const templateParts = this.templateParser.parseTemplate(template);
606
624
  let renderModule;
607
625
  if (this.vite) {
608
- renderModule = await this.vite.ssrLoadModule("/src/entry-server.tsx");
626
+ renderModule = await this.vite.ssrLoadModule(this.entryServerPath);
609
627
  } else {
610
628
  if (this.serverManifest) {
611
629
  const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
@@ -621,12 +639,13 @@ var RenderService = class _RenderService {
621
639
  }
622
640
  }
623
641
  const { data: pageData, __context: context } = data;
624
- const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, viewPath);
642
+ const componentName = viewComponent.displayName || viewComponent.name || "Component";
643
+ const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, componentName);
625
644
  const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
626
645
  const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
627
646
  const headTags = this.templateParser.buildHeadTags(head);
628
647
  let didError = false;
629
- const { pipe, abort } = renderModule.renderComponentStream(viewPath, data, {
648
+ const { pipe, abort } = renderModule.renderComponentStream(viewComponent, data, {
630
649
  onShellReady: /* @__PURE__ */ __name(() => {
631
650
  shellReadyTime = Date.now();
632
651
  res.statusCode = didError ? 500 : 200;
@@ -639,15 +658,15 @@ var RenderService = class _RenderService {
639
658
  pipe(res);
640
659
  if (this.isDevelopment) {
641
660
  const ttfb = shellReadyTime - startTime;
642
- this.logger.log(`[SSR] ${viewPath} shell ready in ${ttfb}ms (stream mode - TTFB)`);
661
+ this.logger.log(`[SSR] ${componentName} shell ready in ${ttfb}ms (stream mode - TTFB)`);
643
662
  }
644
663
  }, "onShellReady"),
645
664
  onShellError: /* @__PURE__ */ __name((error) => {
646
- this.streamingErrorHandler.handleShellError(error, res, viewPath, this.isDevelopment);
665
+ this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
647
666
  }, "onShellError"),
648
667
  onError: /* @__PURE__ */ __name((error) => {
649
668
  didError = true;
650
- this.streamingErrorHandler.handleStreamError(error, viewPath);
669
+ this.streamingErrorHandler.handleStreamError(error, componentName);
651
670
  }, "onError"),
652
671
  onAllReady: /* @__PURE__ */ __name(() => {
653
672
  res.write(inlineScripts);
@@ -658,7 +677,7 @@ var RenderService = class _RenderService {
658
677
  if (this.isDevelopment) {
659
678
  const totalTime = Date.now() - startTime;
660
679
  const streamTime = Date.now() - shellReadyTime;
661
- this.logger.log(`[SSR] ${viewPath} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)`);
680
+ this.logger.log(`[SSR] ${componentName} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)`);
662
681
  }
663
682
  }, "onAllReady")
664
683
  });
@@ -666,7 +685,8 @@ var RenderService = class _RenderService {
666
685
  abort();
667
686
  });
668
687
  } catch (error) {
669
- this.streamingErrorHandler.handleShellError(error, res, viewPath, this.isDevelopment);
688
+ const componentName = typeof viewComponent === "function" ? viewComponent.name : String(viewComponent);
689
+ this.streamingErrorHandler.handleShellError(error, res, componentName, this.isDevelopment);
670
690
  }
671
691
  }
672
692
  };
@@ -713,8 +733,8 @@ var RenderInterceptor = class {
713
733
  this.renderService = renderService;
714
734
  }
715
735
  intercept(context, next) {
716
- const viewPath = this.reflector.get(RENDER_KEY, context.getHandler());
717
- if (!viewPath) {
736
+ const viewPathOrComponent = this.reflector.get(RENDER_KEY, context.getHandler());
737
+ if (!viewPathOrComponent) {
718
738
  return next.handle();
719
739
  }
720
740
  return next.handle().pipe(switchMap(async (data) => {
@@ -738,7 +758,7 @@ var RenderInterceptor = class {
738
758
  __context: renderContext
739
759
  };
740
760
  try {
741
- const html = await this.renderService.render(viewPath, fullData, response, renderResponse.head);
761
+ const html = await this.renderService.render(viewPathOrComponent, fullData, response, renderResponse.head);
742
762
  if (html !== void 0) {
743
763
  response.type("text/html");
744
764
  return html;
@@ -784,6 +804,7 @@ var ViteInitializerService = class _ViteInitializerService {
784
804
  logger = new Logger(_ViteInitializerService.name);
785
805
  viteMode;
786
806
  vitePort;
807
+ viteServer = null;
787
808
  constructor(renderService, httpAdapterHost, viteConfig) {
788
809
  this.renderService = renderService;
789
810
  this.httpAdapterHost = httpAdapterHost;
@@ -801,15 +822,15 @@ var ViteInitializerService = class _ViteInitializerService {
801
822
  async setupDevelopmentMode() {
802
823
  try {
803
824
  const { createServer: createViteServer } = await import('vite');
804
- const vite = await createViteServer({
825
+ this.viteServer = await createViteServer({
805
826
  server: {
806
827
  middlewareMode: true
807
828
  },
808
829
  appType: "custom"
809
830
  });
810
- this.renderService.setViteServer(vite);
831
+ this.renderService.setViteServer(this.viteServer);
811
832
  if (this.viteMode === "embedded") {
812
- await this.mountViteMiddleware(vite);
833
+ await this.mountViteMiddleware(this.viteServer);
813
834
  } else if (this.viteMode === "proxy") {
814
835
  await this.setupViteProxy();
815
836
  }
@@ -872,6 +893,20 @@ var ViteInitializerService = class _ViteInitializerService {
872
893
  this.logger.warn(`Failed to setup static assets: ${error.message}`);
873
894
  }
874
895
  }
896
+ /**
897
+ * Cleanup: Close Vite server on module destroy
898
+ * This prevents port conflicts on hot reload
899
+ */
900
+ async onModuleDestroy() {
901
+ if (this.viteServer) {
902
+ try {
903
+ await this.viteServer.close();
904
+ this.logger.log("\u2713 Vite server closed");
905
+ } catch (error) {
906
+ this.logger.warn(`Failed to close Vite server: ${error.message}`);
907
+ }
908
+ }
909
+ }
875
910
  };
876
911
  ViteInitializerService = _ts_decorate5([
877
912
  Injectable(),