@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,419 @@
|
|
|
1
|
+
import { DynamicModule, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
2
|
+
import { ComponentType } from 'react';
|
|
3
|
+
import { ViteDevServer } from 'vite';
|
|
4
|
+
import { Response } from 'express';
|
|
5
|
+
import { Reflector } from '@nestjs/core';
|
|
6
|
+
import { Observable } from 'rxjs';
|
|
7
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* HTML head data for SEO and page metadata
|
|
11
|
+
*/
|
|
12
|
+
interface HeadData {
|
|
13
|
+
/** Page title (appears in browser tab and search results) */
|
|
14
|
+
title?: string;
|
|
15
|
+
/** Page description for search engines */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** Page keywords (legacy, less important for modern SEO) */
|
|
18
|
+
keywords?: string;
|
|
19
|
+
/** Canonical URL for duplicate content */
|
|
20
|
+
canonical?: string;
|
|
21
|
+
/** Open Graph title for social media sharing */
|
|
22
|
+
ogTitle?: string;
|
|
23
|
+
/** Open Graph description for social media sharing */
|
|
24
|
+
ogDescription?: string;
|
|
25
|
+
/** Open Graph image URL for social media previews */
|
|
26
|
+
ogImage?: string;
|
|
27
|
+
/** Additional link tags (fonts, icons, preloads, etc.) */
|
|
28
|
+
links?: Array<{
|
|
29
|
+
rel: string;
|
|
30
|
+
href: string;
|
|
31
|
+
as?: string;
|
|
32
|
+
type?: string;
|
|
33
|
+
crossorigin?: string;
|
|
34
|
+
[key: string]: any;
|
|
35
|
+
}>;
|
|
36
|
+
/** Additional meta tags */
|
|
37
|
+
meta?: Array<{
|
|
38
|
+
name?: string;
|
|
39
|
+
property?: string;
|
|
40
|
+
content: string;
|
|
41
|
+
[key: string]: any;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Response structure for SSR rendering
|
|
46
|
+
*
|
|
47
|
+
* Can be returned from controllers decorated with @Render.
|
|
48
|
+
* For backwards compatibility, controllers can also return plain objects
|
|
49
|
+
* which will be auto-wrapped as { props: data }.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // Simple case - just props (auto-wrapped)
|
|
54
|
+
* @Render('views/home')
|
|
55
|
+
* getHome() {
|
|
56
|
+
* return { message: 'Hello' };
|
|
57
|
+
* // Treated as: { props: { message: 'Hello' } }
|
|
58
|
+
* }
|
|
59
|
+
*
|
|
60
|
+
* // Advanced case - with head data
|
|
61
|
+
* @Render('views/user')
|
|
62
|
+
* getUser(@Param('id') id: string) {
|
|
63
|
+
* const user = await this.userService.findOne(id);
|
|
64
|
+
* return {
|
|
65
|
+
* props: { user },
|
|
66
|
+
* head: {
|
|
67
|
+
* title: `${user.name} - Profile`,
|
|
68
|
+
* description: user.bio,
|
|
69
|
+
* ogImage: user.avatar
|
|
70
|
+
* }
|
|
71
|
+
* };
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
interface RenderResponse<T = any> {
|
|
76
|
+
/** Props passed to the React component */
|
|
77
|
+
props: T;
|
|
78
|
+
/** HTML head data (title, meta tags, links) */
|
|
79
|
+
head?: HeadData;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* SSR rendering mode configuration
|
|
84
|
+
*/
|
|
85
|
+
type SSRMode = 'string' | 'stream';
|
|
86
|
+
/**
|
|
87
|
+
* Props for development error page component
|
|
88
|
+
*/
|
|
89
|
+
interface ErrorPageDevelopmentProps$1 {
|
|
90
|
+
error: Error;
|
|
91
|
+
viewPath: string;
|
|
92
|
+
phase: 'shell' | 'streaming';
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Vite development mode configuration
|
|
96
|
+
*/
|
|
97
|
+
type ViteMode = 'proxy' | 'embedded';
|
|
98
|
+
/**
|
|
99
|
+
* Vite configuration options
|
|
100
|
+
*/
|
|
101
|
+
interface ViteConfig {
|
|
102
|
+
/**
|
|
103
|
+
* Vite mode for development
|
|
104
|
+
* - 'embedded': Vite runs inside NestJS (no HMR, simplest setup) - DEFAULT
|
|
105
|
+
* - 'proxy': External Vite server with HMR support (requires running `vite` separately)
|
|
106
|
+
*
|
|
107
|
+
* @default 'embedded'
|
|
108
|
+
*/
|
|
109
|
+
mode?: ViteMode;
|
|
110
|
+
/**
|
|
111
|
+
* Port where external Vite dev server is running (proxy mode only)
|
|
112
|
+
*
|
|
113
|
+
* @default 5173
|
|
114
|
+
*/
|
|
115
|
+
port?: number;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Configuration options for the render module
|
|
119
|
+
*/
|
|
120
|
+
interface RenderConfig {
|
|
121
|
+
/**
|
|
122
|
+
* SSR rendering mode
|
|
123
|
+
* - 'string': Traditional renderToString (simple, proven, easier debugging)
|
|
124
|
+
* - 'stream': Modern renderToPipeableStream (better performance, progressive rendering)
|
|
125
|
+
*
|
|
126
|
+
* @default 'string'
|
|
127
|
+
*/
|
|
128
|
+
mode?: SSRMode;
|
|
129
|
+
/**
|
|
130
|
+
* Timeout in milliseconds for SSR rendering.
|
|
131
|
+
* If rendering takes longer than this, the request will be aborted.
|
|
132
|
+
*
|
|
133
|
+
* @default 10000 (10 seconds)
|
|
134
|
+
*/
|
|
135
|
+
timeout?: number;
|
|
136
|
+
/**
|
|
137
|
+
* Vite configuration for development
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* // Zero config - embedded mode by default (simplest)
|
|
142
|
+
* @Module({
|
|
143
|
+
* imports: [RenderModule],
|
|
144
|
+
* })
|
|
145
|
+
*
|
|
146
|
+
* // Proxy mode - external Vite with HMR
|
|
147
|
+
* @Module({
|
|
148
|
+
* imports: [
|
|
149
|
+
* RenderModule.register({
|
|
150
|
+
* vite: { mode: 'proxy', port: 5173 }
|
|
151
|
+
* })
|
|
152
|
+
* ],
|
|
153
|
+
* })
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
vite?: ViteConfig;
|
|
157
|
+
/**
|
|
158
|
+
* Custom error page component for development environment
|
|
159
|
+
* Receives error details and renders custom error UI
|
|
160
|
+
*
|
|
161
|
+
* @default ErrorPageDevelopment from '@shared/render/error-pages'
|
|
162
|
+
*/
|
|
163
|
+
errorPageDevelopment?: ComponentType<ErrorPageDevelopmentProps$1>;
|
|
164
|
+
/**
|
|
165
|
+
* Custom error page component for production environment
|
|
166
|
+
* Renders generic error without sensitive details
|
|
167
|
+
*
|
|
168
|
+
* @default ErrorPageProduction from '@shared/render/error-pages'
|
|
169
|
+
*/
|
|
170
|
+
errorPageProduction?: ComponentType;
|
|
171
|
+
/**
|
|
172
|
+
* Default head data for all pages
|
|
173
|
+
* Can be overridden per-page by returning head in controller
|
|
174
|
+
*
|
|
175
|
+
* For dynamic default head (e.g., from database), use registerAsync
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* RenderModule.register({
|
|
180
|
+
* defaultHead: {
|
|
181
|
+
* title: 'My App',
|
|
182
|
+
* description: 'Default description',
|
|
183
|
+
* links: [{ rel: 'icon', href: '/favicon.ico' }]
|
|
184
|
+
* }
|
|
185
|
+
* })
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
defaultHead?: HeadData;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Template parts for streaming SSR
|
|
192
|
+
* Template is split into parts that are written around the React stream
|
|
193
|
+
*/
|
|
194
|
+
interface TemplateParts {
|
|
195
|
+
/** HTML start through <body> tag */
|
|
196
|
+
htmlStart: string;
|
|
197
|
+
/** Opening <div id="root"> tag */
|
|
198
|
+
rootStart: string;
|
|
199
|
+
/** Closing </div> tag for root */
|
|
200
|
+
rootEnd: string;
|
|
201
|
+
/** Closing </body></html> tags */
|
|
202
|
+
htmlEnd: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
declare class RenderModule {
|
|
206
|
+
/**
|
|
207
|
+
* Register the render module with optional configuration
|
|
208
|
+
*
|
|
209
|
+
* @param config - Optional render configuration
|
|
210
|
+
* @returns Dynamic module
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* // Zero config - embedded mode by default (simplest)
|
|
215
|
+
* @Module({
|
|
216
|
+
* imports: [RenderModule],
|
|
217
|
+
* })
|
|
218
|
+
*
|
|
219
|
+
* // Enable HMR with proxy mode
|
|
220
|
+
* @Module({
|
|
221
|
+
* imports: [
|
|
222
|
+
* RenderModule.register({
|
|
223
|
+
* vite: { mode: 'proxy', port: 5173 }
|
|
224
|
+
* })
|
|
225
|
+
* ],
|
|
226
|
+
* })
|
|
227
|
+
*
|
|
228
|
+
* // Enable streaming SSR
|
|
229
|
+
* RenderModule.register({ mode: 'stream' })
|
|
230
|
+
*
|
|
231
|
+
* // Custom error pages
|
|
232
|
+
* RenderModule.register({
|
|
233
|
+
* mode: 'stream',
|
|
234
|
+
* errorPageDevelopment: MyCustomDevErrorPage,
|
|
235
|
+
* errorPageProduction: MyCustomProdErrorPage,
|
|
236
|
+
* })
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
static register(config?: RenderConfig): DynamicModule;
|
|
240
|
+
/**
|
|
241
|
+
* Register the render module asynchronously with dynamic configuration
|
|
242
|
+
*
|
|
243
|
+
* Use this when you need to inject services (e.g., load config from database)
|
|
244
|
+
*
|
|
245
|
+
* @param options - Async configuration options
|
|
246
|
+
* @returns Dynamic module
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* // Load default head from database
|
|
251
|
+
* RenderModule.registerAsync({
|
|
252
|
+
* imports: [TenantModule],
|
|
253
|
+
* inject: [TenantRepository],
|
|
254
|
+
* useFactory: async (tenantRepo: TenantRepository) => {
|
|
255
|
+
* const tenant = await tenantRepo.findDefaultTenant();
|
|
256
|
+
* return {
|
|
257
|
+
* defaultHead: {
|
|
258
|
+
* title: tenant.appName,
|
|
259
|
+
* description: tenant.description,
|
|
260
|
+
* links: [
|
|
261
|
+
* { rel: 'icon', href: tenant.favicon }
|
|
262
|
+
* ]
|
|
263
|
+
* }
|
|
264
|
+
* };
|
|
265
|
+
* }
|
|
266
|
+
* })
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
static registerAsync(options: {
|
|
270
|
+
imports?: any[];
|
|
271
|
+
inject?: any[];
|
|
272
|
+
useFactory: (...args: any[]) => Promise<RenderConfig> | RenderConfig;
|
|
273
|
+
}): DynamicModule;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Service for parsing HTML templates and building inline scripts for SSR
|
|
278
|
+
*/
|
|
279
|
+
declare class TemplateParserService {
|
|
280
|
+
private readonly headTagRenderers;
|
|
281
|
+
/**
|
|
282
|
+
* Parse HTML template into parts for streaming SSR
|
|
283
|
+
*
|
|
284
|
+
* Splits the template at strategic injection points:
|
|
285
|
+
* - Before root div: Shell HTML (head, body start)
|
|
286
|
+
* - Root div start
|
|
287
|
+
* - Root div end
|
|
288
|
+
* - After root: Scripts and closing tags
|
|
289
|
+
*/
|
|
290
|
+
parseTemplate(html: string): TemplateParts;
|
|
291
|
+
/**
|
|
292
|
+
* Build inline script that provides initial state to the client
|
|
293
|
+
*
|
|
294
|
+
* Safely serializes data using serialize-javascript to avoid XSS vulnerabilities.
|
|
295
|
+
* This library handles all edge cases including escaping dangerous characters,
|
|
296
|
+
* functions, dates, regexes, and prevents prototype pollution.
|
|
297
|
+
*/
|
|
298
|
+
buildInlineScripts(data: any, context: any, componentPath: string): string;
|
|
299
|
+
/**
|
|
300
|
+
* Get client script tag for hydration
|
|
301
|
+
*
|
|
302
|
+
* In development: Direct module import with Vite HMR
|
|
303
|
+
* In production: Hashed filename from manifest
|
|
304
|
+
*/
|
|
305
|
+
getClientScriptTag(isDevelopment: boolean, manifest?: any): string;
|
|
306
|
+
/**
|
|
307
|
+
* Get stylesheet link tags
|
|
308
|
+
*
|
|
309
|
+
* In development: Direct link to source CSS file
|
|
310
|
+
* In production: Hashed CSS files from manifest
|
|
311
|
+
*/
|
|
312
|
+
getStylesheetTags(isDevelopment: boolean, manifest?: any): string;
|
|
313
|
+
/**
|
|
314
|
+
* Build HTML head tags from HeadData
|
|
315
|
+
*
|
|
316
|
+
* Generates title, meta tags, and link tags for SEO and page metadata.
|
|
317
|
+
* Safely escapes content using escape-html to prevent XSS.
|
|
318
|
+
*/
|
|
319
|
+
buildHeadTags(head?: HeadData): string;
|
|
320
|
+
/**
|
|
321
|
+
* Build an HTML tag from an object of attributes
|
|
322
|
+
*/
|
|
323
|
+
private buildTag;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Error handling strategies for streaming SSR
|
|
328
|
+
*
|
|
329
|
+
* Streaming has different error phases:
|
|
330
|
+
* 1. Shell errors: Before any content sent (can send 500)
|
|
331
|
+
* 2. Stream errors: After headers sent (can only log)
|
|
332
|
+
* 3. Client errors: Handled by ErrorBoundary
|
|
333
|
+
*/
|
|
334
|
+
declare class StreamingErrorHandler {
|
|
335
|
+
private readonly errorPageDevelopment?;
|
|
336
|
+
private readonly errorPageProduction?;
|
|
337
|
+
private readonly logger;
|
|
338
|
+
constructor(errorPageDevelopment?: ComponentType<ErrorPageDevelopmentProps$1> | undefined, errorPageProduction?: ComponentType | undefined);
|
|
339
|
+
/**
|
|
340
|
+
* Handle error that occurred before shell was ready
|
|
341
|
+
* Can still set HTTP status code and send error page
|
|
342
|
+
*/
|
|
343
|
+
handleShellError(error: Error, res: Response, viewPath: string, isDevelopment: boolean): void;
|
|
344
|
+
/**
|
|
345
|
+
* Handle error that occurred during streaming
|
|
346
|
+
* Headers already sent, can only log the error
|
|
347
|
+
*/
|
|
348
|
+
handleStreamError(error: Error, viewPath: string): void;
|
|
349
|
+
/**
|
|
350
|
+
* Render development error page using React component
|
|
351
|
+
*/
|
|
352
|
+
private renderDevelopmentErrorPage;
|
|
353
|
+
/**
|
|
354
|
+
* Render production error page using React component
|
|
355
|
+
*/
|
|
356
|
+
private renderProductionErrorPage;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
declare class RenderService {
|
|
360
|
+
private readonly templateParser;
|
|
361
|
+
private readonly streamingErrorHandler;
|
|
362
|
+
private readonly defaultHead?;
|
|
363
|
+
private readonly logger;
|
|
364
|
+
private vite;
|
|
365
|
+
private template;
|
|
366
|
+
private manifest;
|
|
367
|
+
private serverManifest;
|
|
368
|
+
private isDevelopment;
|
|
369
|
+
private ssrMode;
|
|
370
|
+
constructor(templateParser: TemplateParserService, streamingErrorHandler: StreamingErrorHandler, ssrMode?: SSRMode, defaultHead?: HeadData | undefined);
|
|
371
|
+
setViteServer(vite: ViteDevServer): void;
|
|
372
|
+
/**
|
|
373
|
+
* Main render method that routes to string or stream mode
|
|
374
|
+
*/
|
|
375
|
+
render(viewPath: string, data?: any, res?: Response, head?: HeadData): Promise<string | void>;
|
|
376
|
+
/**
|
|
377
|
+
* Merge default head with page-specific head
|
|
378
|
+
* Page-specific head values override defaults
|
|
379
|
+
*/
|
|
380
|
+
private mergeHead;
|
|
381
|
+
/**
|
|
382
|
+
* Traditional string-based SSR using renderToString
|
|
383
|
+
*/
|
|
384
|
+
private renderToString;
|
|
385
|
+
/**
|
|
386
|
+
* Modern streaming SSR using renderToPipeableStream
|
|
387
|
+
*/
|
|
388
|
+
private renderToStream;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
declare class RenderInterceptor implements NestInterceptor {
|
|
392
|
+
private reflector;
|
|
393
|
+
private renderService;
|
|
394
|
+
constructor(reflector: Reflector, renderService: RenderService);
|
|
395
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
interface ErrorPageDevelopmentProps {
|
|
399
|
+
error: Error;
|
|
400
|
+
viewPath: string;
|
|
401
|
+
phase: 'shell' | 'streaming';
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Default development error page component
|
|
405
|
+
*
|
|
406
|
+
* Shows detailed error information with stack trace
|
|
407
|
+
* App developers can override this by providing their own component
|
|
408
|
+
*/
|
|
409
|
+
declare function ErrorPageDevelopment({ error, viewPath, phase, }: ErrorPageDevelopmentProps): react_jsx_runtime.JSX.Element;
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Default production error page component
|
|
413
|
+
*
|
|
414
|
+
* Shows generic error message without sensitive details
|
|
415
|
+
* App developers can override this by providing their own component
|
|
416
|
+
*/
|
|
417
|
+
declare function ErrorPageProduction(): react_jsx_runtime.JSX.Element;
|
|
418
|
+
|
|
419
|
+
export { ErrorPageDevelopment as E, type HeadData as H, RenderModule as R, StreamingErrorHandler as S, TemplateParserService as T, RenderService as a, RenderInterceptor as b, type RenderConfig as c, type SSRMode as d, type RenderResponse as e, ErrorPageProduction as f };
|