@nestjs-ssr/react 0.1.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.
@@ -0,0 +1,1102 @@
1
+ import { Injectable, Logger, Optional, Inject, Global, Module } from '@nestjs/common';
2
+ import { HttpAdapterHost, APP_INTERCEPTOR, Reflector } from '@nestjs/core';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import serialize from 'serialize-javascript';
6
+ import escapeHtml from 'escape-html';
7
+ import { renderToStaticMarkup } from 'react-dom/server';
8
+ import { createElement } from 'react';
9
+ import { switchMap } from 'rxjs/operators';
10
+
11
+ var __defProp = Object.defineProperty;
12
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
13
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
14
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
15
+ }) : x)(function(x) {
16
+ if (typeof require !== "undefined") return require.apply(this, arguments);
17
+ throw Error('Dynamic require of "' + x + '" is not supported');
18
+ });
19
+ function _ts_decorate(decorators, target, key, desc) {
20
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
21
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
22
+ 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;
23
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
24
+ }
25
+ __name(_ts_decorate, "_ts_decorate");
26
+ var TemplateParserService = class {
27
+ static {
28
+ __name(this, "TemplateParserService");
29
+ }
30
+ // Mapping of HeadData fields to their HTML tag renderers
31
+ // Order matters: title and description first for SEO best practices
32
+ headTagRenderers = [
33
+ {
34
+ key: "title",
35
+ render: /* @__PURE__ */ __name((v) => `<title>${escapeHtml(v)}</title>`, "render")
36
+ },
37
+ {
38
+ key: "description",
39
+ render: /* @__PURE__ */ __name((v) => `<meta name="description" content="${escapeHtml(v)}" />`, "render")
40
+ },
41
+ {
42
+ key: "keywords",
43
+ render: /* @__PURE__ */ __name((v) => `<meta name="keywords" content="${escapeHtml(v)}" />`, "render")
44
+ },
45
+ {
46
+ key: "canonical",
47
+ render: /* @__PURE__ */ __name((v) => `<link rel="canonical" href="${escapeHtml(v)}" />`, "render")
48
+ },
49
+ {
50
+ key: "ogTitle",
51
+ render: /* @__PURE__ */ __name((v) => `<meta property="og:title" content="${escapeHtml(v)}" />`, "render")
52
+ },
53
+ {
54
+ key: "ogDescription",
55
+ render: /* @__PURE__ */ __name((v) => `<meta property="og:description" content="${escapeHtml(v)}" />`, "render")
56
+ },
57
+ {
58
+ key: "ogImage",
59
+ render: /* @__PURE__ */ __name((v) => `<meta property="og:image" content="${escapeHtml(v)}" />`, "render")
60
+ }
61
+ ];
62
+ /**
63
+ * Parse HTML template into parts for streaming SSR
64
+ *
65
+ * Splits the template at strategic injection points:
66
+ * - Before root div: Shell HTML (head, body start)
67
+ * - Root div start
68
+ * - Root div end
69
+ * - After root: Scripts and closing tags
70
+ */
71
+ parseTemplate(html) {
72
+ const rootStartMarker = '<div id="root">';
73
+ const rootStartIndex = html.indexOf(rootStartMarker);
74
+ if (rootStartIndex === -1) {
75
+ throw new Error('Template must contain <div id="root">');
76
+ }
77
+ const commentMarker = "<!--app-html-->";
78
+ const commentIndex = html.indexOf(commentMarker, rootStartIndex);
79
+ if (commentIndex === -1) {
80
+ throw new Error("Template must contain <!--app-html--> placeholder");
81
+ }
82
+ const rootEndMarker = "</div>";
83
+ const rootEndIndex = html.indexOf(rootEndMarker, commentIndex);
84
+ if (rootEndIndex === -1) {
85
+ throw new Error("Template must have closing </div> for root");
86
+ }
87
+ const htmlStart = html.substring(0, rootStartIndex);
88
+ const rootStart = rootStartMarker;
89
+ const rootEnd = rootEndMarker;
90
+ const htmlEnd = html.substring(rootEndIndex + rootEndMarker.length);
91
+ return {
92
+ htmlStart,
93
+ rootStart,
94
+ rootEnd,
95
+ htmlEnd
96
+ };
97
+ }
98
+ /**
99
+ * Build inline script that provides initial state to the client
100
+ *
101
+ * Safely serializes data using serialize-javascript to avoid XSS vulnerabilities.
102
+ * This library handles all edge cases including escaping dangerous characters,
103
+ * functions, dates, regexes, and prevents prototype pollution.
104
+ */
105
+ buildInlineScripts(data, context, componentPath) {
106
+ return `<script>
107
+ window.__INITIAL_STATE__ = ${serialize(data, {
108
+ isJSON: true
109
+ })};
110
+ window.__CONTEXT__ = ${serialize(context, {
111
+ isJSON: true
112
+ })};
113
+ window.__COMPONENT_PATH__ = ${serialize(componentPath, {
114
+ isJSON: true
115
+ })};
116
+ </script>`;
117
+ }
118
+ /**
119
+ * Get client script tag for hydration
120
+ *
121
+ * In development: Direct module import with Vite HMR
122
+ * In production: Hashed filename from manifest
123
+ */
124
+ getClientScriptTag(isDevelopment, manifest) {
125
+ if (isDevelopment) {
126
+ return '<script type="module" src="/src/entry-client.tsx"></script>';
127
+ }
128
+ if (!manifest || !manifest["src/entry-client.tsx"]) {
129
+ throw new Error("Manifest missing entry for src/entry-client.tsx");
130
+ }
131
+ const entryFile = manifest["src/entry-client.tsx"].file;
132
+ return `<script type="module" src="/${entryFile}"></script>`;
133
+ }
134
+ /**
135
+ * Get stylesheet link tags
136
+ *
137
+ * In development: Direct link to source CSS file
138
+ * In production: Hashed CSS files from manifest
139
+ */
140
+ getStylesheetTags(isDevelopment, manifest) {
141
+ if (isDevelopment) {
142
+ return "";
143
+ }
144
+ if (!manifest || !manifest["src/entry-client.tsx"]) {
145
+ return "";
146
+ }
147
+ const entry = manifest["src/entry-client.tsx"];
148
+ if (!entry.css || entry.css.length === 0) {
149
+ return "";
150
+ }
151
+ return entry.css.map((css) => `<link rel="stylesheet" href="/${css}" />`).join("\n ");
152
+ }
153
+ /**
154
+ * Build HTML head tags from HeadData
155
+ *
156
+ * Generates title, meta tags, and link tags for SEO and page metadata.
157
+ * Safely escapes content using escape-html to prevent XSS.
158
+ */
159
+ buildHeadTags(head) {
160
+ if (!head) {
161
+ return "";
162
+ }
163
+ const tags = [];
164
+ for (const { key, render } of this.headTagRenderers) {
165
+ const value = head[key];
166
+ if (value && typeof value === "string") {
167
+ tags.push(render(value));
168
+ }
169
+ }
170
+ if (head.links?.length) {
171
+ tags.push(...head.links.map((link) => this.buildTag("link", link)));
172
+ }
173
+ if (head.meta?.length) {
174
+ tags.push(...head.meta.map((meta) => this.buildTag("meta", meta)));
175
+ }
176
+ return tags.join("\n ");
177
+ }
178
+ /**
179
+ * Build an HTML tag from an object of attributes
180
+ */
181
+ buildTag(tagName, attrs) {
182
+ const attrString = Object.entries(attrs).map(([key, value]) => `${key}="${escapeHtml(String(value))}"`).join(" ");
183
+ return `<${tagName} ${attrString} />`;
184
+ }
185
+ };
186
+ TemplateParserService = _ts_decorate([
187
+ Injectable()
188
+ ], TemplateParserService);
189
+
190
+ // src/render/error-pages/error-page-development.tsx
191
+ function ErrorPageDevelopment({ error, viewPath, phase }) {
192
+ const stackLines = error.stack ? error.stack.split("\n").slice(1) : [];
193
+ return /* @__PURE__ */ React.createElement("html", {
194
+ lang: "en"
195
+ }, /* @__PURE__ */ React.createElement("head", null, /* @__PURE__ */ React.createElement("meta", {
196
+ charSet: "UTF-8"
197
+ }), /* @__PURE__ */ React.createElement("meta", {
198
+ name: "viewport",
199
+ content: "width=device-width, initial-scale=1.0"
200
+ }), /* @__PURE__ */ React.createElement("title", null, `SSR Error - ${error.name}`), /* @__PURE__ */ React.createElement("style", {
201
+ dangerouslySetInnerHTML: {
202
+ __html: `
203
+ body {
204
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
205
+ line-height: 1.6;
206
+ padding: 2rem;
207
+ background: #1a1a1a;
208
+ color: #e0e0e0;
209
+ }
210
+ .error-container {
211
+ max-width: 900px;
212
+ margin: 0 auto;
213
+ }
214
+ h1 {
215
+ color: #ff6b6b;
216
+ font-size: 2rem;
217
+ margin-bottom: 0.5rem;
218
+ }
219
+ .error-type {
220
+ color: #ffa502;
221
+ font-size: 1.2rem;
222
+ margin-bottom: 1rem;
223
+ }
224
+ .error-message {
225
+ background: #2d2d2d;
226
+ padding: 1rem;
227
+ border-left: 4px solid #ff6b6b;
228
+ margin: 1rem 0;
229
+ font-family: 'Courier New', Courier, monospace;
230
+ }
231
+ .stack-trace {
232
+ background: #2d2d2d;
233
+ padding: 1rem;
234
+ border-radius: 4px;
235
+ overflow-x: auto;
236
+ margin: 1rem 0;
237
+ }
238
+ .stack-trace pre {
239
+ margin: 0;
240
+ font-family: 'Courier New', Courier, monospace;
241
+ font-size: 0.9rem;
242
+ color: #a0a0a0;
243
+ }
244
+ .meta {
245
+ color: #888;
246
+ font-size: 0.9rem;
247
+ margin-top: 2rem;
248
+ }
249
+ `
250
+ }
251
+ })), /* @__PURE__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement("div", {
252
+ className: "error-container"
253
+ }, /* @__PURE__ */ React.createElement("h1", null, "Server-Side Rendering Error"), /* @__PURE__ */ React.createElement("div", {
254
+ className: "error-type"
255
+ }, error.name), /* @__PURE__ */ React.createElement("div", {
256
+ className: "error-message"
257
+ }, error.message), /* @__PURE__ */ React.createElement("h2", null, "Stack Trace"), /* @__PURE__ */ React.createElement("div", {
258
+ className: "stack-trace"
259
+ }, /* @__PURE__ */ React.createElement("pre", null, stackLines.join("\n"))), /* @__PURE__ */ React.createElement("div", {
260
+ className: "meta"
261
+ }, /* @__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")))));
262
+ }
263
+ __name(ErrorPageDevelopment, "ErrorPageDevelopment");
264
+
265
+ // src/render/error-pages/error-page-production.tsx
266
+ function ErrorPageProduction() {
267
+ return /* @__PURE__ */ React.createElement("html", {
268
+ lang: "en"
269
+ }, /* @__PURE__ */ React.createElement("head", null, /* @__PURE__ */ React.createElement("meta", {
270
+ charSet: "UTF-8"
271
+ }), /* @__PURE__ */ React.createElement("meta", {
272
+ name: "viewport",
273
+ content: "width=device-width, initial-scale=1.0"
274
+ }), /* @__PURE__ */ React.createElement("title", null, "Error"), /* @__PURE__ */ React.createElement("style", {
275
+ dangerouslySetInnerHTML: {
276
+ __html: `
277
+ body {
278
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
279
+ display: flex;
280
+ align-items: center;
281
+ justify-content: center;
282
+ min-height: 100vh;
283
+ margin: 0;
284
+ background: #f5f5f5;
285
+ }
286
+ .error-container {
287
+ text-align: center;
288
+ padding: 2rem;
289
+ }
290
+ h1 {
291
+ font-size: 3rem;
292
+ color: #333;
293
+ margin: 0 0 1rem 0;
294
+ }
295
+ p {
296
+ font-size: 1.2rem;
297
+ color: #666;
298
+ }
299
+ `
300
+ }
301
+ })), /* @__PURE__ */ React.createElement("body", null, /* @__PURE__ */ React.createElement("div", {
302
+ className: "error-container"
303
+ }, /* @__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."))));
304
+ }
305
+ __name(ErrorPageProduction, "ErrorPageProduction");
306
+
307
+ // src/render/streaming-error-handler.ts
308
+ function _ts_decorate2(decorators, target, key, desc) {
309
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
310
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
311
+ 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;
312
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
313
+ }
314
+ __name(_ts_decorate2, "_ts_decorate");
315
+ function _ts_metadata(k, v) {
316
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
317
+ }
318
+ __name(_ts_metadata, "_ts_metadata");
319
+ function _ts_param(paramIndex, decorator) {
320
+ return function(target, key) {
321
+ decorator(target, key, paramIndex);
322
+ };
323
+ }
324
+ __name(_ts_param, "_ts_param");
325
+ var StreamingErrorHandler = class _StreamingErrorHandler {
326
+ static {
327
+ __name(this, "StreamingErrorHandler");
328
+ }
329
+ errorPageDevelopment;
330
+ errorPageProduction;
331
+ logger = new Logger(_StreamingErrorHandler.name);
332
+ constructor(errorPageDevelopment, errorPageProduction) {
333
+ this.errorPageDevelopment = errorPageDevelopment;
334
+ this.errorPageProduction = errorPageProduction;
335
+ }
336
+ /**
337
+ * Handle error that occurred before shell was ready
338
+ * Can still set HTTP status code and send error page
339
+ */
340
+ handleShellError(error, res, viewPath, isDevelopment) {
341
+ this.logger.error(`Shell error rendering ${viewPath}: ${error.message}`, error.stack);
342
+ res.statusCode = 500;
343
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
344
+ if (isDevelopment) {
345
+ res.send(this.renderDevelopmentErrorPage(error, viewPath, "shell"));
346
+ } else {
347
+ res.send(this.renderProductionErrorPage());
348
+ }
349
+ }
350
+ /**
351
+ * Handle error that occurred during streaming
352
+ * Headers already sent, can only log the error
353
+ */
354
+ handleStreamError(error, viewPath) {
355
+ this.logger.error(`Streaming error rendering ${viewPath}: ${error.message}`, error.stack);
356
+ }
357
+ /**
358
+ * Render development error page using React component
359
+ */
360
+ renderDevelopmentErrorPage(error, viewPath, phase) {
361
+ const ErrorComponent = this.errorPageDevelopment || ErrorPageDevelopment;
362
+ const element = createElement(ErrorComponent, {
363
+ error,
364
+ viewPath,
365
+ phase
366
+ });
367
+ return "<!DOCTYPE html>\n" + renderToStaticMarkup(element);
368
+ }
369
+ /**
370
+ * Render production error page using React component
371
+ */
372
+ renderProductionErrorPage() {
373
+ const ErrorComponent = this.errorPageProduction || ErrorPageProduction;
374
+ const element = createElement(ErrorComponent);
375
+ return "<!DOCTYPE html>\n" + renderToStaticMarkup(element);
376
+ }
377
+ };
378
+ StreamingErrorHandler = _ts_decorate2([
379
+ Injectable(),
380
+ _ts_param(0, Optional()),
381
+ _ts_param(0, Inject("ERROR_PAGE_DEVELOPMENT")),
382
+ _ts_param(1, Optional()),
383
+ _ts_param(1, Inject("ERROR_PAGE_PRODUCTION")),
384
+ _ts_metadata("design:type", Function),
385
+ _ts_metadata("design:paramtypes", [
386
+ typeof ComponentType === "undefined" ? Object : ComponentType,
387
+ typeof ComponentType === "undefined" ? Object : ComponentType
388
+ ])
389
+ ], StreamingErrorHandler);
390
+
391
+ // src/render/render.service.ts
392
+ function _ts_decorate3(decorators, target, key, desc) {
393
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
394
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
395
+ 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;
396
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
397
+ }
398
+ __name(_ts_decorate3, "_ts_decorate");
399
+ function _ts_metadata2(k, v) {
400
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
401
+ }
402
+ __name(_ts_metadata2, "_ts_metadata");
403
+ function _ts_param2(paramIndex, decorator) {
404
+ return function(target, key) {
405
+ decorator(target, key, paramIndex);
406
+ };
407
+ }
408
+ __name(_ts_param2, "_ts_param");
409
+ var RenderService = class _RenderService {
410
+ static {
411
+ __name(this, "RenderService");
412
+ }
413
+ templateParser;
414
+ streamingErrorHandler;
415
+ defaultHead;
416
+ logger = new Logger(_RenderService.name);
417
+ vite = null;
418
+ template;
419
+ manifest = null;
420
+ serverManifest = null;
421
+ isDevelopment;
422
+ ssrMode;
423
+ constructor(templateParser, streamingErrorHandler, ssrMode, defaultHead) {
424
+ this.templateParser = templateParser;
425
+ this.streamingErrorHandler = streamingErrorHandler;
426
+ this.defaultHead = defaultHead;
427
+ this.isDevelopment = process.env.NODE_ENV !== "production";
428
+ this.ssrMode = ssrMode || process.env.SSR_MODE || "string";
429
+ let templatePath;
430
+ if (this.isDevelopment) {
431
+ const packageTemplatePaths = [
432
+ join(__dirname, "../templates/index.html"),
433
+ join(__dirname, "../src/templates/index.html"),
434
+ join(__dirname, "../../src/templates/index.html")
435
+ ];
436
+ const localTemplatePath = join(process.cwd(), "src/views/index.html");
437
+ const foundPackageTemplate = packageTemplatePaths.find((p) => existsSync(p));
438
+ if (foundPackageTemplate) {
439
+ templatePath = foundPackageTemplate;
440
+ } else if (existsSync(localTemplatePath)) {
441
+ templatePath = localTemplatePath;
442
+ } else {
443
+ throw new Error(`Template file not found. Tried:
444
+ ` + packageTemplatePaths.map((p) => ` - ${p} (package template)`).join("\n") + `
445
+ - ${localTemplatePath} (local template)`);
446
+ }
447
+ } else {
448
+ templatePath = join(process.cwd(), "dist/client/index.html");
449
+ if (!existsSync(templatePath)) {
450
+ throw new Error(`Template file not found at ${templatePath}. Make sure to run the build process first.`);
451
+ }
452
+ }
453
+ try {
454
+ this.template = readFileSync(templatePath, "utf-8");
455
+ this.logger.log(`\u2713 Loaded template from ${templatePath}`);
456
+ } catch (error) {
457
+ throw new Error(`Failed to read template file at ${templatePath}: ${error.message}`);
458
+ }
459
+ if (!this.isDevelopment) {
460
+ const manifestPath = join(process.cwd(), "dist/client/.vite/manifest.json");
461
+ if (existsSync(manifestPath)) {
462
+ this.manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
463
+ } else {
464
+ this.logger.warn("\u26A0\uFE0F Client manifest not found. Run `pnpm build:client` first.");
465
+ }
466
+ const serverManifestPath = join(process.cwd(), "dist/server/.vite/manifest.json");
467
+ if (existsSync(serverManifestPath)) {
468
+ this.serverManifest = JSON.parse(readFileSync(serverManifestPath, "utf-8"));
469
+ } else {
470
+ this.logger.warn("\u26A0\uFE0F Server manifest not found. Run `pnpm build:server` first.");
471
+ }
472
+ }
473
+ }
474
+ setViteServer(vite) {
475
+ this.vite = vite;
476
+ }
477
+ /**
478
+ * Main render method that routes to string or stream mode
479
+ */
480
+ async render(viewPath, data = {}, res, head) {
481
+ const mergedHead = this.mergeHead(this.defaultHead, head);
482
+ if (this.ssrMode === "stream") {
483
+ if (!res) {
484
+ throw new Error("Response object is required for streaming SSR mode. Pass res as third parameter.");
485
+ }
486
+ return this.renderToStream(viewPath, data, res, mergedHead);
487
+ }
488
+ return this.renderToString(viewPath, data, mergedHead);
489
+ }
490
+ /**
491
+ * Merge default head with page-specific head
492
+ * Page-specific head values override defaults
493
+ */
494
+ mergeHead(defaultHead, pageHead) {
495
+ if (!defaultHead && !pageHead) {
496
+ return void 0;
497
+ }
498
+ return {
499
+ ...defaultHead,
500
+ ...pageHead,
501
+ // Merge arrays (links and meta) instead of replacing
502
+ links: [
503
+ ...defaultHead?.links || [],
504
+ ...pageHead?.links || []
505
+ ],
506
+ meta: [
507
+ ...defaultHead?.meta || [],
508
+ ...pageHead?.meta || []
509
+ ]
510
+ };
511
+ }
512
+ /**
513
+ * Traditional string-based SSR using renderToString
514
+ */
515
+ async renderToString(viewPath, data = {}, head) {
516
+ const startTime = Date.now();
517
+ try {
518
+ let template = this.template;
519
+ if (this.vite) {
520
+ template = await this.vite.transformIndexHtml("/", template);
521
+ }
522
+ let renderModule;
523
+ if (this.vite) {
524
+ renderModule = await this.vite.ssrLoadModule("/src/entry-server.tsx");
525
+ } else {
526
+ if (this.serverManifest) {
527
+ const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
528
+ if (manifestEntry) {
529
+ const [, entry] = manifestEntry;
530
+ const serverPath = join(process.cwd(), "dist/server", entry.file);
531
+ renderModule = await import(serverPath);
532
+ } else {
533
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
534
+ }
535
+ } else {
536
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
537
+ }
538
+ }
539
+ const { data: pageData, __context: context } = data;
540
+ const appHtml = await renderModule.renderComponent(viewPath, data);
541
+ const initialStateScript = `
542
+ <script>
543
+ window.__INITIAL_STATE__ = ${serialize(pageData, {
544
+ isJSON: true
545
+ })};
546
+ window.__CONTEXT__ = ${serialize(context, {
547
+ isJSON: true
548
+ })};
549
+ window.__COMPONENT_PATH__ = ${serialize(viewPath, {
550
+ isJSON: true
551
+ })};
552
+ </script>
553
+ `;
554
+ let clientScript = "";
555
+ let styles = "";
556
+ if (this.vite) {
557
+ clientScript = `<script type="module" src="/src/entry-client.tsx"></script>`;
558
+ styles = "";
559
+ } else {
560
+ if (this.manifest) {
561
+ const manifestEntry = Object.entries(this.manifest).find(([key, value]) => value.isEntry && key.includes("entry-client"));
562
+ if (manifestEntry) {
563
+ const [, entry] = manifestEntry;
564
+ const entryFile = entry.file;
565
+ clientScript = `<script type="module" src="/${entryFile}"></script>`;
566
+ if (entry.css) {
567
+ const cssFiles = entry.css;
568
+ styles = cssFiles.map((css) => `<link rel="stylesheet" href="/${css}" />`).join("\n ");
569
+ }
570
+ } else {
571
+ this.logger.error("\u26A0\uFE0F Client entry not found in manifest");
572
+ clientScript = `<script type="module" src="/assets/client.js"></script>`;
573
+ }
574
+ } else {
575
+ this.logger.error("\u26A0\uFE0F Client manifest not found");
576
+ clientScript = `<script type="module" src="/assets/client.js"></script>`;
577
+ }
578
+ }
579
+ const headTags = this.templateParser.buildHeadTags(head);
580
+ let html = template.replace("<!--app-html-->", appHtml);
581
+ html = html.replace("<!--initial-state-->", initialStateScript);
582
+ html = html.replace("<!--client-scripts-->", clientScript);
583
+ html = html.replace("<!--styles-->", styles);
584
+ html = html.replace("<!--head-meta-->", headTags);
585
+ if (this.isDevelopment) {
586
+ const duration = Date.now() - startTime;
587
+ this.logger.log(`[SSR] ${viewPath} rendered in ${duration}ms (string mode)`);
588
+ }
589
+ return html;
590
+ } catch (error) {
591
+ throw error;
592
+ }
593
+ }
594
+ /**
595
+ * Modern streaming SSR using renderToPipeableStream
596
+ */
597
+ async renderToStream(viewPath, data = {}, res, head) {
598
+ const startTime = Date.now();
599
+ let shellReadyTime = 0;
600
+ try {
601
+ let template = this.template;
602
+ if (this.vite) {
603
+ template = await this.vite.transformIndexHtml("/", template);
604
+ }
605
+ const templateParts = this.templateParser.parseTemplate(template);
606
+ let renderModule;
607
+ if (this.vite) {
608
+ renderModule = await this.vite.ssrLoadModule("/src/entry-server.tsx");
609
+ } else {
610
+ if (this.serverManifest) {
611
+ const manifestEntry = Object.entries(this.serverManifest).find(([key, value]) => value.isEntry && key.includes("entry-server"));
612
+ if (manifestEntry) {
613
+ const [, entry] = manifestEntry;
614
+ const serverPath = join(process.cwd(), "dist/server", entry.file);
615
+ renderModule = await import(serverPath);
616
+ } else {
617
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
618
+ }
619
+ } else {
620
+ throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
621
+ }
622
+ }
623
+ const { data: pageData, __context: context } = data;
624
+ const inlineScripts = this.templateParser.buildInlineScripts(pageData, context, viewPath);
625
+ const clientScript = this.templateParser.getClientScriptTag(this.isDevelopment, this.manifest);
626
+ const stylesheetTags = this.templateParser.getStylesheetTags(this.isDevelopment, this.manifest);
627
+ const headTags = this.templateParser.buildHeadTags(head);
628
+ let didError = false;
629
+ const { pipe, abort } = renderModule.renderComponentStream(viewPath, data, {
630
+ onShellReady: /* @__PURE__ */ __name(() => {
631
+ shellReadyTime = Date.now();
632
+ res.statusCode = didError ? 500 : 200;
633
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
634
+ let htmlStart = templateParts.htmlStart;
635
+ htmlStart = htmlStart.replace("<!--styles-->", stylesheetTags);
636
+ htmlStart = htmlStart.replace("<!--head-meta-->", headTags);
637
+ res.write(htmlStart);
638
+ res.write(templateParts.rootStart);
639
+ pipe(res);
640
+ if (this.isDevelopment) {
641
+ const ttfb = shellReadyTime - startTime;
642
+ this.logger.log(`[SSR] ${viewPath} shell ready in ${ttfb}ms (stream mode - TTFB)`);
643
+ }
644
+ }, "onShellReady"),
645
+ onShellError: /* @__PURE__ */ __name((error) => {
646
+ this.streamingErrorHandler.handleShellError(error, res, viewPath, this.isDevelopment);
647
+ }, "onShellError"),
648
+ onError: /* @__PURE__ */ __name((error) => {
649
+ didError = true;
650
+ this.streamingErrorHandler.handleStreamError(error, viewPath);
651
+ }, "onError"),
652
+ onAllReady: /* @__PURE__ */ __name(() => {
653
+ res.write(inlineScripts);
654
+ res.write(clientScript);
655
+ res.write(templateParts.rootEnd);
656
+ res.write(templateParts.htmlEnd);
657
+ res.end();
658
+ if (this.isDevelopment) {
659
+ const totalTime = Date.now() - startTime;
660
+ const streamTime = Date.now() - shellReadyTime;
661
+ this.logger.log(`[SSR] ${viewPath} streaming complete in ${totalTime}ms total (${streamTime}ms streaming)`);
662
+ }
663
+ }, "onAllReady")
664
+ });
665
+ res.on("close", () => {
666
+ abort();
667
+ });
668
+ } catch (error) {
669
+ this.streamingErrorHandler.handleShellError(error, res, viewPath, this.isDevelopment);
670
+ }
671
+ }
672
+ };
673
+ RenderService = _ts_decorate3([
674
+ Injectable(),
675
+ _ts_param2(2, Optional()),
676
+ _ts_param2(2, Inject("SSR_MODE")),
677
+ _ts_param2(3, Optional()),
678
+ _ts_param2(3, Inject("DEFAULT_HEAD")),
679
+ _ts_metadata2("design:type", Function),
680
+ _ts_metadata2("design:paramtypes", [
681
+ typeof TemplateParserService === "undefined" ? Object : TemplateParserService,
682
+ typeof StreamingErrorHandler === "undefined" ? Object : StreamingErrorHandler,
683
+ typeof SSRMode === "undefined" ? Object : SSRMode,
684
+ typeof HeadData === "undefined" ? Object : HeadData
685
+ ])
686
+ ], RenderService);
687
+ var RENDER_KEY = "render";
688
+
689
+ // src/render/render.interceptor.ts
690
+ function _ts_decorate4(decorators, target, key, desc) {
691
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
692
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
693
+ 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;
694
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
695
+ }
696
+ __name(_ts_decorate4, "_ts_decorate");
697
+ function _ts_metadata3(k, v) {
698
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
699
+ }
700
+ __name(_ts_metadata3, "_ts_metadata");
701
+ function isRenderResponse(data) {
702
+ return data && typeof data === "object" && "props" in data;
703
+ }
704
+ __name(isRenderResponse, "isRenderResponse");
705
+ var RenderInterceptor = class {
706
+ static {
707
+ __name(this, "RenderInterceptor");
708
+ }
709
+ reflector;
710
+ renderService;
711
+ constructor(reflector, renderService) {
712
+ this.reflector = reflector;
713
+ this.renderService = renderService;
714
+ }
715
+ intercept(context, next) {
716
+ const viewPath = this.reflector.get(RENDER_KEY, context.getHandler());
717
+ if (!viewPath) {
718
+ return next.handle();
719
+ }
720
+ return next.handle().pipe(switchMap(async (data) => {
721
+ const httpContext = context.switchToHttp();
722
+ const request = httpContext.getRequest();
723
+ const response = httpContext.getResponse();
724
+ const renderContext = {
725
+ url: request.url,
726
+ path: request.path,
727
+ query: request.query,
728
+ params: request.params,
729
+ userAgent: request.headers["user-agent"],
730
+ acceptLanguage: request.headers["accept-language"],
731
+ referer: request.headers.referer
732
+ };
733
+ const renderResponse = isRenderResponse(data) ? data : {
734
+ props: data
735
+ };
736
+ const fullData = {
737
+ data: renderResponse.props,
738
+ __context: renderContext
739
+ };
740
+ try {
741
+ const html = await this.renderService.render(viewPath, fullData, response, renderResponse.head);
742
+ if (html !== void 0) {
743
+ response.type("text/html");
744
+ return html;
745
+ }
746
+ return;
747
+ } catch (error) {
748
+ throw error;
749
+ }
750
+ }));
751
+ }
752
+ };
753
+ RenderInterceptor = _ts_decorate4([
754
+ Injectable(),
755
+ _ts_metadata3("design:type", Function),
756
+ _ts_metadata3("design:paramtypes", [
757
+ typeof Reflector === "undefined" ? Object : Reflector,
758
+ typeof RenderService === "undefined" ? Object : RenderService
759
+ ])
760
+ ], RenderInterceptor);
761
+ function _ts_decorate5(decorators, target, key, desc) {
762
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
763
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
764
+ 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;
765
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
766
+ }
767
+ __name(_ts_decorate5, "_ts_decorate");
768
+ function _ts_metadata4(k, v) {
769
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
770
+ }
771
+ __name(_ts_metadata4, "_ts_metadata");
772
+ function _ts_param3(paramIndex, decorator) {
773
+ return function(target, key) {
774
+ decorator(target, key, paramIndex);
775
+ };
776
+ }
777
+ __name(_ts_param3, "_ts_param");
778
+ var ViteInitializerService = class _ViteInitializerService {
779
+ static {
780
+ __name(this, "ViteInitializerService");
781
+ }
782
+ renderService;
783
+ httpAdapterHost;
784
+ logger = new Logger(_ViteInitializerService.name);
785
+ viteMode;
786
+ vitePort;
787
+ constructor(renderService, httpAdapterHost, viteConfig) {
788
+ this.renderService = renderService;
789
+ this.httpAdapterHost = httpAdapterHost;
790
+ this.viteMode = viteConfig?.mode || "embedded";
791
+ this.vitePort = viteConfig?.port || 5173;
792
+ }
793
+ async onModuleInit() {
794
+ const isDevelopment = process.env.NODE_ENV !== "production";
795
+ if (isDevelopment) {
796
+ await this.setupDevelopmentMode();
797
+ } else {
798
+ this.setupProductionMode();
799
+ }
800
+ }
801
+ async setupDevelopmentMode() {
802
+ try {
803
+ const { createServer: createViteServer } = await import('vite');
804
+ const vite = await createViteServer({
805
+ server: {
806
+ middlewareMode: true
807
+ },
808
+ appType: "custom"
809
+ });
810
+ this.renderService.setViteServer(vite);
811
+ if (this.viteMode === "embedded") {
812
+ await this.mountViteMiddleware(vite);
813
+ } else if (this.viteMode === "proxy") {
814
+ await this.setupViteProxy();
815
+ }
816
+ this.logger.log(`\u2713 Vite initialized for SSR (mode: ${this.viteMode})`);
817
+ } catch (error) {
818
+ this.logger.warn(`Failed to initialize Vite: ${error.message}. Make sure vite is installed.`);
819
+ }
820
+ }
821
+ async mountViteMiddleware(vite) {
822
+ try {
823
+ const httpAdapter = this.httpAdapterHost.httpAdapter;
824
+ if (!httpAdapter) {
825
+ this.logger.warn("HTTP adapter not available, skipping Vite middleware setup");
826
+ return;
827
+ }
828
+ const app = httpAdapter.getInstance();
829
+ app.use(vite.middlewares);
830
+ this.logger.log(`\u2713 Vite middleware mounted (embedded mode with HMR)`);
831
+ } catch (error) {
832
+ this.logger.warn(`Failed to mount Vite middleware: ${error.message}`);
833
+ }
834
+ }
835
+ async setupViteProxy() {
836
+ try {
837
+ const httpAdapter = this.httpAdapterHost.httpAdapter;
838
+ if (!httpAdapter) {
839
+ this.logger.warn("HTTP adapter not available, skipping Vite proxy setup");
840
+ return;
841
+ }
842
+ const app = httpAdapter.getInstance();
843
+ const { createProxyMiddleware } = await import('http-proxy-middleware');
844
+ const viteProxy = createProxyMiddleware({
845
+ target: `http://localhost:${this.vitePort}`,
846
+ changeOrigin: true,
847
+ ws: true,
848
+ pathFilter: /* @__PURE__ */ __name((pathname) => {
849
+ return pathname.startsWith("/src/") || pathname.startsWith("/@") || pathname.startsWith("/node_modules/");
850
+ }, "pathFilter")
851
+ });
852
+ app.use(viteProxy);
853
+ this.logger.log(`\u2713 Vite HMR proxy configured (external Vite on port ${this.vitePort})`);
854
+ } catch (error) {
855
+ this.logger.warn(`Failed to setup Vite proxy: ${error.message}. Make sure http-proxy-middleware is installed.`);
856
+ }
857
+ }
858
+ setupProductionMode() {
859
+ try {
860
+ const httpAdapter = this.httpAdapterHost.httpAdapter;
861
+ if (httpAdapter) {
862
+ const app = httpAdapter.getInstance();
863
+ const { join: join2 } = __require("path");
864
+ const express = __require("express");
865
+ app.use(express.static(join2(process.cwd(), "dist/client"), {
866
+ index: false,
867
+ maxAge: "1y"
868
+ }));
869
+ this.logger.log("\u2713 Static assets configured (dist/client)");
870
+ }
871
+ } catch (error) {
872
+ this.logger.warn(`Failed to setup static assets: ${error.message}`);
873
+ }
874
+ }
875
+ };
876
+ ViteInitializerService = _ts_decorate5([
877
+ Injectable(),
878
+ _ts_param3(2, Optional()),
879
+ _ts_param3(2, Inject("VITE_CONFIG")),
880
+ _ts_metadata4("design:type", Function),
881
+ _ts_metadata4("design:paramtypes", [
882
+ typeof RenderService === "undefined" ? Object : RenderService,
883
+ typeof HttpAdapterHost === "undefined" ? Object : HttpAdapterHost,
884
+ typeof ViteConfig === "undefined" ? Object : ViteConfig
885
+ ])
886
+ ], ViteInitializerService);
887
+
888
+ // src/render/render.module.ts
889
+ function _ts_decorate6(decorators, target, key, desc) {
890
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
891
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
892
+ 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;
893
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
894
+ }
895
+ __name(_ts_decorate6, "_ts_decorate");
896
+ var RenderModule = class _RenderModule {
897
+ static {
898
+ __name(this, "RenderModule");
899
+ }
900
+ /**
901
+ * Register the render module with optional configuration
902
+ *
903
+ * @param config - Optional render configuration
904
+ * @returns Dynamic module
905
+ *
906
+ * @example
907
+ * ```ts
908
+ * // Zero config - embedded mode by default (simplest)
909
+ * @Module({
910
+ * imports: [RenderModule],
911
+ * })
912
+ *
913
+ * // Enable HMR with proxy mode
914
+ * @Module({
915
+ * imports: [
916
+ * RenderModule.register({
917
+ * vite: { mode: 'proxy', port: 5173 }
918
+ * })
919
+ * ],
920
+ * })
921
+ *
922
+ * // Enable streaming SSR
923
+ * RenderModule.register({ mode: 'stream' })
924
+ *
925
+ * // Custom error pages
926
+ * RenderModule.register({
927
+ * mode: 'stream',
928
+ * errorPageDevelopment: MyCustomDevErrorPage,
929
+ * errorPageProduction: MyCustomProdErrorPage,
930
+ * })
931
+ * ```
932
+ */
933
+ static register(config) {
934
+ const providers = [
935
+ RenderService,
936
+ TemplateParserService,
937
+ StreamingErrorHandler,
938
+ ViteInitializerService,
939
+ {
940
+ provide: APP_INTERCEPTOR,
941
+ useClass: RenderInterceptor
942
+ }
943
+ ];
944
+ providers.push({
945
+ provide: "VITE_CONFIG",
946
+ useValue: config?.vite || {}
947
+ });
948
+ if (config?.mode) {
949
+ providers.push({
950
+ provide: "SSR_MODE",
951
+ useValue: config.mode
952
+ });
953
+ }
954
+ if (config?.errorPageDevelopment) {
955
+ providers.push({
956
+ provide: "ERROR_PAGE_DEVELOPMENT",
957
+ useValue: config.errorPageDevelopment
958
+ });
959
+ }
960
+ if (config?.errorPageProduction) {
961
+ providers.push({
962
+ provide: "ERROR_PAGE_PRODUCTION",
963
+ useValue: config.errorPageProduction
964
+ });
965
+ }
966
+ if (config?.defaultHead) {
967
+ providers.push({
968
+ provide: "DEFAULT_HEAD",
969
+ useValue: config.defaultHead
970
+ });
971
+ }
972
+ return {
973
+ global: true,
974
+ module: _RenderModule,
975
+ providers,
976
+ exports: [
977
+ RenderService
978
+ ]
979
+ };
980
+ }
981
+ /**
982
+ * Register the render module asynchronously with dynamic configuration
983
+ *
984
+ * Use this when you need to inject services (e.g., load config from database)
985
+ *
986
+ * @param options - Async configuration options
987
+ * @returns Dynamic module
988
+ *
989
+ * @example
990
+ * ```ts
991
+ * // Load default head from database
992
+ * RenderModule.registerAsync({
993
+ * imports: [TenantModule],
994
+ * inject: [TenantRepository],
995
+ * useFactory: async (tenantRepo: TenantRepository) => {
996
+ * const tenant = await tenantRepo.findDefaultTenant();
997
+ * return {
998
+ * defaultHead: {
999
+ * title: tenant.appName,
1000
+ * description: tenant.description,
1001
+ * links: [
1002
+ * { rel: 'icon', href: tenant.favicon }
1003
+ * ]
1004
+ * }
1005
+ * };
1006
+ * }
1007
+ * })
1008
+ * ```
1009
+ */
1010
+ static registerAsync(options) {
1011
+ const configProvider = {
1012
+ provide: "RENDER_CONFIG",
1013
+ useFactory: options.useFactory,
1014
+ inject: options.inject || []
1015
+ };
1016
+ const providers = [
1017
+ configProvider,
1018
+ RenderService,
1019
+ TemplateParserService,
1020
+ StreamingErrorHandler,
1021
+ ViteInitializerService,
1022
+ {
1023
+ provide: APP_INTERCEPTOR,
1024
+ useClass: RenderInterceptor
1025
+ },
1026
+ // Vite configuration provider - reads from config
1027
+ {
1028
+ provide: "VITE_CONFIG",
1029
+ useFactory: /* @__PURE__ */ __name((config) => config?.vite || {}, "useFactory"),
1030
+ inject: [
1031
+ "RENDER_CONFIG"
1032
+ ]
1033
+ },
1034
+ // SSR mode provider - reads from config
1035
+ {
1036
+ provide: "SSR_MODE",
1037
+ useFactory: /* @__PURE__ */ __name((config) => config?.mode, "useFactory"),
1038
+ inject: [
1039
+ "RENDER_CONFIG"
1040
+ ]
1041
+ },
1042
+ // Error page providers - read from config
1043
+ {
1044
+ provide: "ERROR_PAGE_DEVELOPMENT",
1045
+ useFactory: /* @__PURE__ */ __name((config) => config?.errorPageDevelopment, "useFactory"),
1046
+ inject: [
1047
+ "RENDER_CONFIG"
1048
+ ]
1049
+ },
1050
+ {
1051
+ provide: "ERROR_PAGE_PRODUCTION",
1052
+ useFactory: /* @__PURE__ */ __name((config) => config?.errorPageProduction, "useFactory"),
1053
+ inject: [
1054
+ "RENDER_CONFIG"
1055
+ ]
1056
+ },
1057
+ // Default head provider - reads from config
1058
+ {
1059
+ provide: "DEFAULT_HEAD",
1060
+ useFactory: /* @__PURE__ */ __name((config) => config?.defaultHead, "useFactory"),
1061
+ inject: [
1062
+ "RENDER_CONFIG"
1063
+ ]
1064
+ }
1065
+ ];
1066
+ return {
1067
+ global: true,
1068
+ module: _RenderModule,
1069
+ imports: options.imports || [],
1070
+ providers,
1071
+ exports: [
1072
+ RenderService
1073
+ ]
1074
+ };
1075
+ }
1076
+ };
1077
+ RenderModule = _ts_decorate6([
1078
+ Global(),
1079
+ Module({
1080
+ providers: [
1081
+ RenderService,
1082
+ TemplateParserService,
1083
+ StreamingErrorHandler,
1084
+ ViteInitializerService,
1085
+ {
1086
+ provide: APP_INTERCEPTOR,
1087
+ useClass: RenderInterceptor
1088
+ },
1089
+ {
1090
+ provide: "VITE_CONFIG",
1091
+ useValue: {}
1092
+ }
1093
+ ],
1094
+ exports: [
1095
+ RenderService
1096
+ ]
1097
+ })
1098
+ ], RenderModule);
1099
+
1100
+ export { ErrorPageDevelopment, ErrorPageProduction, RenderInterceptor, RenderModule, RenderService, StreamingErrorHandler, TemplateParserService };
1101
+ //# sourceMappingURL=index.mjs.map
1102
+ //# sourceMappingURL=index.mjs.map