@nestjs-ssr/react 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.mts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +50 -8
- package/dist/client.mjs +50 -8
- package/dist/{index-DdE--mA2.d.mts → index-CiYcz-1T.d.mts} +89 -4
- package/dist/{index-BzOLOiIZ.d.ts → index-Dq2qZSge.d.ts} +89 -4
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +54 -4
- package/dist/index.mjs +55 -5
- package/dist/render/index.d.mts +3 -3
- package/dist/render/index.d.ts +3 -3
- package/dist/render/index.js +35 -4
- package/dist/render/index.mjs +36 -5
- package/dist/{render-response.interface-CxbuKGnV.d.mts → render-response.interface-ClWJXKL4.d.mts} +19 -10
- package/dist/{render-response.interface-CxbuKGnV.d.ts → render-response.interface-ClWJXKL4.d.ts} +19 -10
- package/dist/templates/entry-client.tsx +23 -4
- package/dist/templates/entry-server.tsx +25 -8
- package/dist/{use-page-context-CGT9woWe.d.mts → use-page-context-CVC9DHcL.d.mts} +2 -1
- package/dist/{use-page-context-05ODF4zW.d.ts → use-page-context-DChgHhL9.d.ts} +2 -1
- package/package.json +1 -1
- package/src/templates/entry-client.tsx +23 -4
- package/src/templates/entry-server.tsx +25 -8
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Injectable, Logger, Optional, Inject, Global, Module, SetMetadata } from '@nestjs/common';
|
|
2
|
-
import { HttpAdapterHost, APP_INTERCEPTOR
|
|
2
|
+
import { Reflector, HttpAdapterHost, APP_INTERCEPTOR } from '@nestjs/core';
|
|
3
3
|
import { existsSync, readFileSync } from 'fs';
|
|
4
4
|
import { join, relative } from 'path';
|
|
5
5
|
import { uneval } from 'devalue';
|
|
@@ -308,9 +308,13 @@ var StringRenderer = class _StringRenderer {
|
|
|
308
308
|
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
|
-
const { data: pageData, __context: pageContext } = data;
|
|
311
|
+
const { data: pageData, __context: pageContext, __layouts: layouts } = data;
|
|
312
312
|
const html = await renderModule.renderSegment(viewComponent, data);
|
|
313
313
|
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
314
|
+
const layoutMetadata = layouts ? layouts.map((l) => ({
|
|
315
|
+
name: l.layout.displayName || l.layout.name || "default",
|
|
316
|
+
props: l.props
|
|
317
|
+
})) : [];
|
|
314
318
|
if (context.isDevelopment) {
|
|
315
319
|
const duration = Date.now() - startTime;
|
|
316
320
|
this.logger.log(`[SSR] ${componentName} segment rendered in ${duration}ms`);
|
|
@@ -321,7 +325,8 @@ var StringRenderer = class _StringRenderer {
|
|
|
321
325
|
props: pageData,
|
|
322
326
|
swapTarget,
|
|
323
327
|
componentName,
|
|
324
|
-
context: pageContext
|
|
328
|
+
context: pageContext,
|
|
329
|
+
layouts: layoutMetadata
|
|
325
330
|
};
|
|
326
331
|
}
|
|
327
332
|
};
|
|
@@ -1090,11 +1095,13 @@ var RenderInterceptor = class {
|
|
|
1090
1095
|
renderService;
|
|
1091
1096
|
allowedHeaders;
|
|
1092
1097
|
allowedCookies;
|
|
1093
|
-
|
|
1098
|
+
contextFactory;
|
|
1099
|
+
constructor(reflector, renderService, allowedHeaders, allowedCookies, contextFactory) {
|
|
1094
1100
|
this.reflector = reflector;
|
|
1095
1101
|
this.renderService = renderService;
|
|
1096
1102
|
this.allowedHeaders = allowedHeaders;
|
|
1097
1103
|
this.allowedCookies = allowedCookies;
|
|
1104
|
+
this.contextFactory = contextFactory;
|
|
1098
1105
|
}
|
|
1099
1106
|
/**
|
|
1100
1107
|
* Resolve the layout hierarchy for a given route
|
|
@@ -1234,6 +1241,14 @@ var RenderInterceptor = class {
|
|
|
1234
1241
|
renderContext.cookies = cookies;
|
|
1235
1242
|
}
|
|
1236
1243
|
}
|
|
1244
|
+
if (this.contextFactory) {
|
|
1245
|
+
const customContext = await this.contextFactory({
|
|
1246
|
+
req: request
|
|
1247
|
+
});
|
|
1248
|
+
if (customContext) {
|
|
1249
|
+
Object.assign(renderContext, customContext);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1237
1252
|
const renderResponse = isRenderResponse(data) ? data : {
|
|
1238
1253
|
props: data
|
|
1239
1254
|
};
|
|
@@ -1280,12 +1295,15 @@ RenderInterceptor = _ts_decorate6([
|
|
|
1280
1295
|
_ts_param3(2, Inject("ALLOWED_HEADERS")),
|
|
1281
1296
|
_ts_param3(3, Optional()),
|
|
1282
1297
|
_ts_param3(3, Inject("ALLOWED_COOKIES")),
|
|
1298
|
+
_ts_param3(4, Optional()),
|
|
1299
|
+
_ts_param3(4, Inject("CONTEXT_FACTORY")),
|
|
1283
1300
|
_ts_metadata5("design:type", Function),
|
|
1284
1301
|
_ts_metadata5("design:paramtypes", [
|
|
1285
1302
|
typeof Reflector === "undefined" ? Object : Reflector,
|
|
1286
1303
|
typeof RenderService === "undefined" ? Object : RenderService,
|
|
1287
1304
|
Array,
|
|
1288
|
-
Array
|
|
1305
|
+
Array,
|
|
1306
|
+
typeof ContextFactory === "undefined" ? Object : ContextFactory
|
|
1289
1307
|
])
|
|
1290
1308
|
], RenderInterceptor);
|
|
1291
1309
|
function _ts_decorate7(decorators, target, key, desc) {
|
|
@@ -1529,6 +1547,12 @@ var RenderModule = class _RenderModule {
|
|
|
1529
1547
|
provide: "ALLOWED_COOKIES",
|
|
1530
1548
|
useValue: config?.allowedCookies || []
|
|
1531
1549
|
});
|
|
1550
|
+
if (config?.context) {
|
|
1551
|
+
providers.push({
|
|
1552
|
+
provide: "CONTEXT_FACTORY",
|
|
1553
|
+
useValue: config.context
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1532
1556
|
return {
|
|
1533
1557
|
global: true,
|
|
1534
1558
|
module: _RenderModule,
|
|
@@ -1647,6 +1671,13 @@ var RenderModule = class _RenderModule {
|
|
|
1647
1671
|
inject: [
|
|
1648
1672
|
"RENDER_CONFIG"
|
|
1649
1673
|
]
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
provide: "CONTEXT_FACTORY",
|
|
1677
|
+
useFactory: /* @__PURE__ */ __name((config) => config?.context, "useFactory"),
|
|
1678
|
+
inject: [
|
|
1679
|
+
"RENDER_CONFIG"
|
|
1680
|
+
]
|
|
1650
1681
|
}
|
|
1651
1682
|
];
|
|
1652
1683
|
return {
|
|
@@ -1709,9 +1740,28 @@ var PageContext = getOrCreateContext();
|
|
|
1709
1740
|
function registerPageContextState(setter) {
|
|
1710
1741
|
}
|
|
1711
1742
|
__name(registerPageContextState, "registerPageContextState");
|
|
1743
|
+
var segmentSetters = /* @__PURE__ */ new Set();
|
|
1744
|
+
function registerSegmentSetter(setter) {
|
|
1745
|
+
segmentSetters.add(setter);
|
|
1746
|
+
}
|
|
1747
|
+
__name(registerSegmentSetter, "registerSegmentSetter");
|
|
1748
|
+
function unregisterSegmentSetter(setter) {
|
|
1749
|
+
segmentSetters.delete(setter);
|
|
1750
|
+
}
|
|
1751
|
+
__name(unregisterSegmentSetter, "unregisterSegmentSetter");
|
|
1752
|
+
function broadcastToSegments(context) {
|
|
1753
|
+
segmentSetters.forEach((setter) => setter(context));
|
|
1754
|
+
}
|
|
1755
|
+
__name(broadcastToSegments, "broadcastToSegments");
|
|
1712
1756
|
function PageContextProvider({ context: initialContext, children, isSegment = false }) {
|
|
1713
1757
|
const [context, setContext] = useState(initialContext);
|
|
1714
1758
|
useEffect(() => {
|
|
1759
|
+
if (!isSegment) {
|
|
1760
|
+
return void 0;
|
|
1761
|
+
} else {
|
|
1762
|
+
registerSegmentSetter(setContext);
|
|
1763
|
+
return () => unregisterSegmentSetter(setContext);
|
|
1764
|
+
}
|
|
1715
1765
|
}, [
|
|
1716
1766
|
isSegment
|
|
1717
1767
|
]);
|
package/dist/render/index.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-
|
|
1
|
+
export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-CiYcz-1T.mjs';
|
|
2
2
|
import '@nestjs/common';
|
|
3
3
|
import 'react';
|
|
4
|
-
import '../render-response.interface-CxbuKGnV.mjs';
|
|
5
|
-
import 'vite';
|
|
6
4
|
import 'express';
|
|
5
|
+
import '../render-response.interface-ClWJXKL4.mjs';
|
|
6
|
+
import 'vite';
|
|
7
7
|
import '@nestjs/core';
|
|
8
8
|
import 'rxjs';
|
|
9
9
|
import 'react/jsx-runtime';
|
package/dist/render/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-
|
|
1
|
+
export { E as ErrorPageDevelopment, e as ErrorPageProduction, b as RenderInterceptor, R as RenderModule, a as RenderService, S as StreamingErrorHandler, T as TemplateParserService } from '../index-Dq2qZSge.js';
|
|
2
2
|
import '@nestjs/common';
|
|
3
3
|
import 'react';
|
|
4
|
-
import '../render-response.interface-CxbuKGnV.js';
|
|
5
|
-
import 'vite';
|
|
6
4
|
import 'express';
|
|
5
|
+
import '../render-response.interface-ClWJXKL4.js';
|
|
6
|
+
import 'vite';
|
|
7
7
|
import '@nestjs/core';
|
|
8
8
|
import 'rxjs';
|
|
9
9
|
import 'react/jsx-runtime';
|
package/dist/render/index.js
CHANGED
|
@@ -314,9 +314,13 @@ var StringRenderer = class _StringRenderer {
|
|
|
314
314
|
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
315
315
|
}
|
|
316
316
|
}
|
|
317
|
-
const { data: pageData, __context: pageContext } = data;
|
|
317
|
+
const { data: pageData, __context: pageContext, __layouts: layouts } = data;
|
|
318
318
|
const html = await renderModule.renderSegment(viewComponent, data);
|
|
319
319
|
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
320
|
+
const layoutMetadata = layouts ? layouts.map((l) => ({
|
|
321
|
+
name: l.layout.displayName || l.layout.name || "default",
|
|
322
|
+
props: l.props
|
|
323
|
+
})) : [];
|
|
320
324
|
if (context.isDevelopment) {
|
|
321
325
|
const duration = Date.now() - startTime;
|
|
322
326
|
this.logger.log(`[SSR] ${componentName} segment rendered in ${duration}ms`);
|
|
@@ -327,7 +331,8 @@ var StringRenderer = class _StringRenderer {
|
|
|
327
331
|
props: pageData,
|
|
328
332
|
swapTarget,
|
|
329
333
|
componentName,
|
|
330
|
-
context: pageContext
|
|
334
|
+
context: pageContext,
|
|
335
|
+
layouts: layoutMetadata
|
|
331
336
|
};
|
|
332
337
|
}
|
|
333
338
|
};
|
|
@@ -1078,11 +1083,13 @@ exports.RenderInterceptor = class RenderInterceptor {
|
|
|
1078
1083
|
renderService;
|
|
1079
1084
|
allowedHeaders;
|
|
1080
1085
|
allowedCookies;
|
|
1081
|
-
|
|
1086
|
+
contextFactory;
|
|
1087
|
+
constructor(reflector, renderService, allowedHeaders, allowedCookies, contextFactory) {
|
|
1082
1088
|
this.reflector = reflector;
|
|
1083
1089
|
this.renderService = renderService;
|
|
1084
1090
|
this.allowedHeaders = allowedHeaders;
|
|
1085
1091
|
this.allowedCookies = allowedCookies;
|
|
1092
|
+
this.contextFactory = contextFactory;
|
|
1086
1093
|
}
|
|
1087
1094
|
/**
|
|
1088
1095
|
* Resolve the layout hierarchy for a given route
|
|
@@ -1222,6 +1229,14 @@ exports.RenderInterceptor = class RenderInterceptor {
|
|
|
1222
1229
|
renderContext.cookies = cookies;
|
|
1223
1230
|
}
|
|
1224
1231
|
}
|
|
1232
|
+
if (this.contextFactory) {
|
|
1233
|
+
const customContext = await this.contextFactory({
|
|
1234
|
+
req: request
|
|
1235
|
+
});
|
|
1236
|
+
if (customContext) {
|
|
1237
|
+
Object.assign(renderContext, customContext);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1225
1240
|
const renderResponse = isRenderResponse(data) ? data : {
|
|
1226
1241
|
props: data
|
|
1227
1242
|
};
|
|
@@ -1268,12 +1283,15 @@ exports.RenderInterceptor = _ts_decorate6([
|
|
|
1268
1283
|
_ts_param3(2, common.Inject("ALLOWED_HEADERS")),
|
|
1269
1284
|
_ts_param3(3, common.Optional()),
|
|
1270
1285
|
_ts_param3(3, common.Inject("ALLOWED_COOKIES")),
|
|
1286
|
+
_ts_param3(4, common.Optional()),
|
|
1287
|
+
_ts_param3(4, common.Inject("CONTEXT_FACTORY")),
|
|
1271
1288
|
_ts_metadata5("design:type", Function),
|
|
1272
1289
|
_ts_metadata5("design:paramtypes", [
|
|
1273
1290
|
typeof core.Reflector === "undefined" ? Object : core.Reflector,
|
|
1274
1291
|
typeof exports.RenderService === "undefined" ? Object : exports.RenderService,
|
|
1275
1292
|
Array,
|
|
1276
|
-
Array
|
|
1293
|
+
Array,
|
|
1294
|
+
typeof ContextFactory === "undefined" ? Object : ContextFactory
|
|
1277
1295
|
])
|
|
1278
1296
|
], exports.RenderInterceptor);
|
|
1279
1297
|
function _ts_decorate7(decorators, target, key, desc) {
|
|
@@ -1517,6 +1535,12 @@ exports.RenderModule = class _RenderModule {
|
|
|
1517
1535
|
provide: "ALLOWED_COOKIES",
|
|
1518
1536
|
useValue: config?.allowedCookies || []
|
|
1519
1537
|
});
|
|
1538
|
+
if (config?.context) {
|
|
1539
|
+
providers.push({
|
|
1540
|
+
provide: "CONTEXT_FACTORY",
|
|
1541
|
+
useValue: config.context
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1520
1544
|
return {
|
|
1521
1545
|
global: true,
|
|
1522
1546
|
module: _RenderModule,
|
|
@@ -1635,6 +1659,13 @@ exports.RenderModule = class _RenderModule {
|
|
|
1635
1659
|
inject: [
|
|
1636
1660
|
"RENDER_CONFIG"
|
|
1637
1661
|
]
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
provide: "CONTEXT_FACTORY",
|
|
1665
|
+
useFactory: /* @__PURE__ */ __name((config) => config?.context, "useFactory"),
|
|
1666
|
+
inject: [
|
|
1667
|
+
"RENDER_CONFIG"
|
|
1668
|
+
]
|
|
1638
1669
|
}
|
|
1639
1670
|
];
|
|
1640
1671
|
return {
|
package/dist/render/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Injectable, Logger, Optional, Inject, Global, Module } from '@nestjs/common';
|
|
2
|
-
import { HttpAdapterHost, APP_INTERCEPTOR
|
|
2
|
+
import { Reflector, HttpAdapterHost, APP_INTERCEPTOR } from '@nestjs/core';
|
|
3
3
|
import { existsSync, readFileSync } from 'fs';
|
|
4
4
|
import { join, relative } from 'path';
|
|
5
5
|
import { uneval } from 'devalue';
|
|
@@ -308,9 +308,13 @@ var StringRenderer = class _StringRenderer {
|
|
|
308
308
|
throw new Error("Server bundle not found in manifest. Run `pnpm build:server` to generate the server bundle.");
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
|
-
const { data: pageData, __context: pageContext } = data;
|
|
311
|
+
const { data: pageData, __context: pageContext, __layouts: layouts } = data;
|
|
312
312
|
const html = await renderModule.renderSegment(viewComponent, data);
|
|
313
313
|
const componentName = viewComponent.displayName || viewComponent.name || "Component";
|
|
314
|
+
const layoutMetadata = layouts ? layouts.map((l) => ({
|
|
315
|
+
name: l.layout.displayName || l.layout.name || "default",
|
|
316
|
+
props: l.props
|
|
317
|
+
})) : [];
|
|
314
318
|
if (context.isDevelopment) {
|
|
315
319
|
const duration = Date.now() - startTime;
|
|
316
320
|
this.logger.log(`[SSR] ${componentName} segment rendered in ${duration}ms`);
|
|
@@ -321,7 +325,8 @@ var StringRenderer = class _StringRenderer {
|
|
|
321
325
|
props: pageData,
|
|
322
326
|
swapTarget,
|
|
323
327
|
componentName,
|
|
324
|
-
context: pageContext
|
|
328
|
+
context: pageContext,
|
|
329
|
+
layouts: layoutMetadata
|
|
325
330
|
};
|
|
326
331
|
}
|
|
327
332
|
};
|
|
@@ -1072,11 +1077,13 @@ var RenderInterceptor = class {
|
|
|
1072
1077
|
renderService;
|
|
1073
1078
|
allowedHeaders;
|
|
1074
1079
|
allowedCookies;
|
|
1075
|
-
|
|
1080
|
+
contextFactory;
|
|
1081
|
+
constructor(reflector, renderService, allowedHeaders, allowedCookies, contextFactory) {
|
|
1076
1082
|
this.reflector = reflector;
|
|
1077
1083
|
this.renderService = renderService;
|
|
1078
1084
|
this.allowedHeaders = allowedHeaders;
|
|
1079
1085
|
this.allowedCookies = allowedCookies;
|
|
1086
|
+
this.contextFactory = contextFactory;
|
|
1080
1087
|
}
|
|
1081
1088
|
/**
|
|
1082
1089
|
* Resolve the layout hierarchy for a given route
|
|
@@ -1216,6 +1223,14 @@ var RenderInterceptor = class {
|
|
|
1216
1223
|
renderContext.cookies = cookies;
|
|
1217
1224
|
}
|
|
1218
1225
|
}
|
|
1226
|
+
if (this.contextFactory) {
|
|
1227
|
+
const customContext = await this.contextFactory({
|
|
1228
|
+
req: request
|
|
1229
|
+
});
|
|
1230
|
+
if (customContext) {
|
|
1231
|
+
Object.assign(renderContext, customContext);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1219
1234
|
const renderResponse = isRenderResponse(data) ? data : {
|
|
1220
1235
|
props: data
|
|
1221
1236
|
};
|
|
@@ -1262,12 +1277,15 @@ RenderInterceptor = _ts_decorate6([
|
|
|
1262
1277
|
_ts_param3(2, Inject("ALLOWED_HEADERS")),
|
|
1263
1278
|
_ts_param3(3, Optional()),
|
|
1264
1279
|
_ts_param3(3, Inject("ALLOWED_COOKIES")),
|
|
1280
|
+
_ts_param3(4, Optional()),
|
|
1281
|
+
_ts_param3(4, Inject("CONTEXT_FACTORY")),
|
|
1265
1282
|
_ts_metadata5("design:type", Function),
|
|
1266
1283
|
_ts_metadata5("design:paramtypes", [
|
|
1267
1284
|
typeof Reflector === "undefined" ? Object : Reflector,
|
|
1268
1285
|
typeof RenderService === "undefined" ? Object : RenderService,
|
|
1269
1286
|
Array,
|
|
1270
|
-
Array
|
|
1287
|
+
Array,
|
|
1288
|
+
typeof ContextFactory === "undefined" ? Object : ContextFactory
|
|
1271
1289
|
])
|
|
1272
1290
|
], RenderInterceptor);
|
|
1273
1291
|
function _ts_decorate7(decorators, target, key, desc) {
|
|
@@ -1511,6 +1529,12 @@ var RenderModule = class _RenderModule {
|
|
|
1511
1529
|
provide: "ALLOWED_COOKIES",
|
|
1512
1530
|
useValue: config?.allowedCookies || []
|
|
1513
1531
|
});
|
|
1532
|
+
if (config?.context) {
|
|
1533
|
+
providers.push({
|
|
1534
|
+
provide: "CONTEXT_FACTORY",
|
|
1535
|
+
useValue: config.context
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1514
1538
|
return {
|
|
1515
1539
|
global: true,
|
|
1516
1540
|
module: _RenderModule,
|
|
@@ -1629,6 +1653,13 @@ var RenderModule = class _RenderModule {
|
|
|
1629
1653
|
inject: [
|
|
1630
1654
|
"RENDER_CONFIG"
|
|
1631
1655
|
]
|
|
1656
|
+
},
|
|
1657
|
+
{
|
|
1658
|
+
provide: "CONTEXT_FACTORY",
|
|
1659
|
+
useFactory: /* @__PURE__ */ __name((config) => config?.context, "useFactory"),
|
|
1660
|
+
inject: [
|
|
1661
|
+
"RENDER_CONFIG"
|
|
1662
|
+
]
|
|
1632
1663
|
}
|
|
1633
1664
|
];
|
|
1634
1665
|
return {
|
package/dist/{render-response.interface-CxbuKGnV.d.mts → render-response.interface-ClWJXKL4.d.mts}
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Contains safe request metadata that can be exposed to the client.
|
|
4
4
|
*
|
|
5
5
|
* Extend this interface to add app-specific properties (user, tenant, feature flags, etc.).
|
|
6
|
-
* Use module configuration to
|
|
6
|
+
* Use the `context` option in module configuration to enrich the context.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* // Basic usage - use as-is
|
|
@@ -32,19 +32,28 @@
|
|
|
32
32
|
* theme?: string; // From cookie
|
|
33
33
|
* }
|
|
34
34
|
*
|
|
35
|
-
* // Configure module to
|
|
36
|
-
*
|
|
35
|
+
* // Configure module with context factory to enrich context
|
|
36
|
+
* RenderModule.forRoot({
|
|
37
37
|
* allowedCookies: ['theme', 'locale'],
|
|
38
38
|
* allowedHeaders: ['x-tenant-id'],
|
|
39
|
+
* context: ({ req }) => ({
|
|
40
|
+
* user: req.user, // From Passport JWT strategy
|
|
41
|
+
* tenant: req.tenant,
|
|
42
|
+
* featureFlags: req.featureFlags,
|
|
43
|
+
* }),
|
|
39
44
|
* })
|
|
40
45
|
*
|
|
41
|
-
* //
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* // Or with async factory (use forRootAsync)
|
|
47
|
+
* RenderModule.forRootAsync({
|
|
48
|
+
* imports: [PermissionModule],
|
|
49
|
+
* inject: [PermissionService],
|
|
50
|
+
* useFactory: (permissionService) => ({
|
|
51
|
+
* context: async ({ req }) => ({
|
|
52
|
+
* user: req.user,
|
|
53
|
+
* permissions: await permissionService.getForUser(req.user),
|
|
54
|
+
* }),
|
|
55
|
+
* }),
|
|
56
|
+
* })
|
|
48
57
|
*/
|
|
49
58
|
interface RenderContext {
|
|
50
59
|
url: string;
|
package/dist/{render-response.interface-CxbuKGnV.d.ts → render-response.interface-ClWJXKL4.d.ts}
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Contains safe request metadata that can be exposed to the client.
|
|
4
4
|
*
|
|
5
5
|
* Extend this interface to add app-specific properties (user, tenant, feature flags, etc.).
|
|
6
|
-
* Use module configuration to
|
|
6
|
+
* Use the `context` option in module configuration to enrich the context.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* // Basic usage - use as-is
|
|
@@ -32,19 +32,28 @@
|
|
|
32
32
|
* theme?: string; // From cookie
|
|
33
33
|
* }
|
|
34
34
|
*
|
|
35
|
-
* // Configure module to
|
|
36
|
-
*
|
|
35
|
+
* // Configure module with context factory to enrich context
|
|
36
|
+
* RenderModule.forRoot({
|
|
37
37
|
* allowedCookies: ['theme', 'locale'],
|
|
38
38
|
* allowedHeaders: ['x-tenant-id'],
|
|
39
|
+
* context: ({ req }) => ({
|
|
40
|
+
* user: req.user, // From Passport JWT strategy
|
|
41
|
+
* tenant: req.tenant,
|
|
42
|
+
* featureFlags: req.featureFlags,
|
|
43
|
+
* }),
|
|
39
44
|
* })
|
|
40
45
|
*
|
|
41
|
-
* //
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* // Or with async factory (use forRootAsync)
|
|
47
|
+
* RenderModule.forRootAsync({
|
|
48
|
+
* imports: [PermissionModule],
|
|
49
|
+
* inject: [PermissionService],
|
|
50
|
+
* useFactory: (permissionService) => ({
|
|
51
|
+
* context: async ({ req }) => ({
|
|
52
|
+
* user: req.user,
|
|
53
|
+
* permissions: await permissionService.getForUser(req.user),
|
|
54
|
+
* }),
|
|
55
|
+
* }),
|
|
56
|
+
* })
|
|
48
57
|
*/
|
|
49
58
|
interface RenderContext {
|
|
50
59
|
url: string;
|
|
@@ -115,8 +115,15 @@ function hasLayout(
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
* Compose a component with its layout (and nested layouts if any)
|
|
119
|
-
* This must match the server-side composition in entry-server.tsx
|
|
118
|
+
* Compose a component with its layout (and nested layouts if any).
|
|
119
|
+
* This must match the server-side composition in entry-server.tsx.
|
|
120
|
+
*
|
|
121
|
+
* The layouts array is ordered [RootLayout, ControllerLayout, MethodLayout] (outer to inner).
|
|
122
|
+
* We iterate in REVERSE order because wrapping happens inside-out:
|
|
123
|
+
* - Start with Page
|
|
124
|
+
* - Wrap with innermost layout first (MethodLayout)
|
|
125
|
+
* - Then wrap with ControllerLayout
|
|
126
|
+
* - Finally wrap with RootLayout (outermost)
|
|
120
127
|
*/
|
|
121
128
|
function composeWithLayout(
|
|
122
129
|
ViewComponent: React.ComponentType<any>,
|
|
@@ -139,9 +146,11 @@ function composeWithLayout(
|
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
148
|
|
|
142
|
-
// Wrap with each layout in
|
|
149
|
+
// Wrap with each layout in REVERSE order (innermost to outermost)
|
|
150
|
+
// This produces the correct nesting: RootLayout > ControllerLayout > Page
|
|
143
151
|
// Must match server-side wrapping with data-layout and data-outlet attributes
|
|
144
|
-
for (
|
|
152
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
153
|
+
const { layout: Layout, props: layoutProps } = layouts[i];
|
|
145
154
|
const layoutName = Layout.displayName || Layout.name || 'Layout';
|
|
146
155
|
result = (
|
|
147
156
|
<div data-layout={layoutName}>
|
|
@@ -183,8 +192,18 @@ hydrateRoot(
|
|
|
183
192
|
<StrictMode>{wrappedElement}</StrictMode>,
|
|
184
193
|
);
|
|
185
194
|
|
|
195
|
+
// Track if initial hydration is complete to ignore false popstate events
|
|
196
|
+
let hydrationComplete = false;
|
|
197
|
+
requestAnimationFrame(() => {
|
|
198
|
+
hydrationComplete = true;
|
|
199
|
+
});
|
|
200
|
+
|
|
186
201
|
// Handle browser back/forward navigation
|
|
187
202
|
window.addEventListener('popstate', async () => {
|
|
203
|
+
// Ignore popstate events that fire before hydration is complete
|
|
204
|
+
// (some browsers fire popstate on initial page load)
|
|
205
|
+
if (!hydrationComplete) return;
|
|
206
|
+
|
|
188
207
|
// Dynamically import navigate to avoid circular dependency with hydrate-segment
|
|
189
208
|
const { navigate } = await import('@nestjs-ssr/react/client');
|
|
190
209
|
// Re-navigate to the current URL (browser already updated location)
|
|
@@ -25,6 +25,13 @@ export function getRootLayout(): React.ComponentType<any> | null {
|
|
|
25
25
|
* Layouts are passed from the RenderInterceptor based on decorators.
|
|
26
26
|
* Each layout is wrapped with data-layout and data-outlet attributes
|
|
27
27
|
* for client-side navigation segment swapping.
|
|
28
|
+
*
|
|
29
|
+
* The layouts array is ordered [RootLayout, ControllerLayout, MethodLayout] (outer to inner).
|
|
30
|
+
* We iterate in REVERSE order because wrapping happens inside-out:
|
|
31
|
+
* - Start with Page
|
|
32
|
+
* - Wrap with innermost layout first (MethodLayout)
|
|
33
|
+
* - Then wrap with ControllerLayout
|
|
34
|
+
* - Finally wrap with RootLayout (outermost)
|
|
28
35
|
*/
|
|
29
36
|
function composeWithLayouts(
|
|
30
37
|
ViewComponent: React.ComponentType<any>,
|
|
@@ -35,11 +42,12 @@ function composeWithLayouts(
|
|
|
35
42
|
// Start with the page component
|
|
36
43
|
let result = <ViewComponent {...props} />;
|
|
37
44
|
|
|
38
|
-
// Wrap with each layout in
|
|
39
|
-
//
|
|
45
|
+
// Wrap with each layout in REVERSE order (innermost to outermost)
|
|
46
|
+
// This produces the correct nesting: RootLayout > ControllerLayout > Page
|
|
40
47
|
// Pass context to layouts so they can access path, params, etc. for navigation
|
|
41
48
|
// Each layout gets data-layout attribute and children are wrapped in data-outlet
|
|
42
|
-
for (
|
|
49
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
50
|
+
const { layout: Layout, props: layoutProps } = layouts[i];
|
|
43
51
|
const layoutName = Layout.displayName || Layout.name || 'Layout';
|
|
44
52
|
result = (
|
|
45
53
|
<div data-layout={layoutName}>
|
|
@@ -80,19 +88,28 @@ export function renderComponent(
|
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
/**
|
|
83
|
-
* Render
|
|
84
|
-
*
|
|
91
|
+
* Render a segment for client-side navigation.
|
|
92
|
+
* Includes any layouts below the swap target (e.g., nested layouts).
|
|
93
|
+
* The swap target's outlet will receive this rendered content.
|
|
85
94
|
*/
|
|
86
95
|
export function renderSegment(
|
|
87
96
|
ViewComponent: React.ComponentType<any>,
|
|
88
97
|
data: any,
|
|
89
98
|
) {
|
|
90
|
-
const { data: pageData, __context: context } = data;
|
|
99
|
+
const { data: pageData, __context: context, __layouts: layouts } = data;
|
|
100
|
+
|
|
101
|
+
// Compose with filtered layouts (layouts below the swap target)
|
|
102
|
+
const composedElement = composeWithLayouts(
|
|
103
|
+
ViewComponent,
|
|
104
|
+
pageData,
|
|
105
|
+
layouts,
|
|
106
|
+
context,
|
|
107
|
+
);
|
|
91
108
|
|
|
92
|
-
//
|
|
109
|
+
// Wrap with PageContextProvider to make context available via hooks
|
|
93
110
|
const element = (
|
|
94
111
|
<PageContextProvider context={context}>
|
|
95
|
-
|
|
112
|
+
{composedElement}
|
|
96
113
|
</PageContextProvider>
|
|
97
114
|
);
|
|
98
115
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { H as HeadData, R as RenderContext } from './render-response.interface-
|
|
1
|
+
import { H as HeadData, R as RenderContext } from './render-response.interface-ClWJXKL4.mjs';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
|
|
@@ -85,6 +85,7 @@ declare function updatePageContext(context: RenderContext): void;
|
|
|
85
85
|
*
|
|
86
86
|
* @param isSegment - If true, this is a segment provider (for hydrated segments)
|
|
87
87
|
* and won't register its setter to avoid overwriting the root provider's.
|
|
88
|
+
* However, it will still receive broadcasts when context updates.
|
|
88
89
|
*/
|
|
89
90
|
declare function PageContextProvider({ context: initialContext, children, isSegment, }: {
|
|
90
91
|
context: RenderContext;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { H as HeadData, R as RenderContext } from './render-response.interface-
|
|
1
|
+
import { H as HeadData, R as RenderContext } from './render-response.interface-ClWJXKL4.js';
|
|
2
2
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
|
|
@@ -85,6 +85,7 @@ declare function updatePageContext(context: RenderContext): void;
|
|
|
85
85
|
*
|
|
86
86
|
* @param isSegment - If true, this is a segment provider (for hydrated segments)
|
|
87
87
|
* and won't register its setter to avoid overwriting the root provider's.
|
|
88
|
+
* However, it will still receive broadcasts when context updates.
|
|
88
89
|
*/
|
|
89
90
|
declare function PageContextProvider({ context: initialContext, children, isSegment, }: {
|
|
90
91
|
context: RenderContext;
|
package/package.json
CHANGED
|
@@ -115,8 +115,15 @@ function hasLayout(
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
* Compose a component with its layout (and nested layouts if any)
|
|
119
|
-
* This must match the server-side composition in entry-server.tsx
|
|
118
|
+
* Compose a component with its layout (and nested layouts if any).
|
|
119
|
+
* This must match the server-side composition in entry-server.tsx.
|
|
120
|
+
*
|
|
121
|
+
* The layouts array is ordered [RootLayout, ControllerLayout, MethodLayout] (outer to inner).
|
|
122
|
+
* We iterate in REVERSE order because wrapping happens inside-out:
|
|
123
|
+
* - Start with Page
|
|
124
|
+
* - Wrap with innermost layout first (MethodLayout)
|
|
125
|
+
* - Then wrap with ControllerLayout
|
|
126
|
+
* - Finally wrap with RootLayout (outermost)
|
|
120
127
|
*/
|
|
121
128
|
function composeWithLayout(
|
|
122
129
|
ViewComponent: React.ComponentType<any>,
|
|
@@ -139,9 +146,11 @@ function composeWithLayout(
|
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
148
|
|
|
142
|
-
// Wrap with each layout in
|
|
149
|
+
// Wrap with each layout in REVERSE order (innermost to outermost)
|
|
150
|
+
// This produces the correct nesting: RootLayout > ControllerLayout > Page
|
|
143
151
|
// Must match server-side wrapping with data-layout and data-outlet attributes
|
|
144
|
-
for (
|
|
152
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
153
|
+
const { layout: Layout, props: layoutProps } = layouts[i];
|
|
145
154
|
const layoutName = Layout.displayName || Layout.name || 'Layout';
|
|
146
155
|
result = (
|
|
147
156
|
<div data-layout={layoutName}>
|
|
@@ -183,8 +192,18 @@ hydrateRoot(
|
|
|
183
192
|
<StrictMode>{wrappedElement}</StrictMode>,
|
|
184
193
|
);
|
|
185
194
|
|
|
195
|
+
// Track if initial hydration is complete to ignore false popstate events
|
|
196
|
+
let hydrationComplete = false;
|
|
197
|
+
requestAnimationFrame(() => {
|
|
198
|
+
hydrationComplete = true;
|
|
199
|
+
});
|
|
200
|
+
|
|
186
201
|
// Handle browser back/forward navigation
|
|
187
202
|
window.addEventListener('popstate', async () => {
|
|
203
|
+
// Ignore popstate events that fire before hydration is complete
|
|
204
|
+
// (some browsers fire popstate on initial page load)
|
|
205
|
+
if (!hydrationComplete) return;
|
|
206
|
+
|
|
188
207
|
// Dynamically import navigate to avoid circular dependency with hydrate-segment
|
|
189
208
|
const { navigate } = await import('@nestjs-ssr/react/client');
|
|
190
209
|
// Re-navigate to the current URL (browser already updated location)
|