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