@nestjs-ssr/react 0.3.3 → 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/cli/init.js +5 -5
- package/dist/cli/init.mjs +5 -5
- 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 +90 -36
- package/dist/index.mjs +61 -7
- package/dist/render/index.d.mts +3 -3
- package/dist/render/index.d.ts +3 -3
- package/dist/render/index.js +58 -24
- package/dist/render/index.mjs +41 -6
- 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 +69 -27
- 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/etc/react.api.md +250 -262
- package/package.json +1 -1
- package/src/templates/entry-client.tsx +69 -27
- package/src/templates/entry-server.tsx +25 -8
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="@nestjs-ssr/react/global" />
|
|
2
|
+
|
|
2
3
|
import React, { StrictMode } from 'react';
|
|
3
4
|
import { hydrateRoot } from 'react-dom/client';
|
|
4
5
|
import {
|
|
@@ -10,6 +11,15 @@ const componentName = window.__COMPONENT_NAME__;
|
|
|
10
11
|
const initialProps = window.__INITIAL_STATE__ || {};
|
|
11
12
|
const renderContext = window.__CONTEXT__ || {};
|
|
12
13
|
|
|
14
|
+
// Auto-discover root layout using Vite's glob import (must match server-side discovery)
|
|
15
|
+
// @ts-ignore - Vite-specific API
|
|
16
|
+
const layoutModules = import.meta.glob('@/views/layout.tsx', {
|
|
17
|
+
eager: true,
|
|
18
|
+
}) as Record<string, { default: React.ComponentType<any> }>;
|
|
19
|
+
|
|
20
|
+
const layoutPath = Object.keys(layoutModules)[0];
|
|
21
|
+
const RootLayout = layoutPath ? layoutModules[layoutPath].default : null;
|
|
22
|
+
|
|
13
23
|
// Auto-import all view components using Vite's glob feature
|
|
14
24
|
// Exclude entry-client.tsx and entry-server.tsx from the glob
|
|
15
25
|
// @ts-ignore - Vite-specific API
|
|
@@ -105,46 +115,68 @@ function hasLayout(
|
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
/**
|
|
108
|
-
* Compose a component with its layout (and nested layouts if any)
|
|
109
|
-
* 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)
|
|
110
127
|
*/
|
|
111
128
|
function composeWithLayout(
|
|
112
129
|
ViewComponent: React.ComponentType<any>,
|
|
113
130
|
props: any,
|
|
131
|
+
context?: any,
|
|
132
|
+
layouts: Array<{ layout: React.ComponentType<any>; props?: any }> = [],
|
|
114
133
|
): React.ReactElement {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
while (hasLayout(currentComponent)) {
|
|
130
|
-
layoutChain.push({
|
|
131
|
-
Layout: currentComponent.layout,
|
|
132
|
-
layoutProps: currentComponent.layoutProps || {},
|
|
133
|
-
});
|
|
134
|
-
currentComponent = currentComponent.layout;
|
|
134
|
+
// Start with the page component
|
|
135
|
+
let result = <ViewComponent {...props} />;
|
|
136
|
+
|
|
137
|
+
// If no layouts passed, check if component has its own layout chain
|
|
138
|
+
if (layouts.length === 0 && hasLayout(ViewComponent)) {
|
|
139
|
+
let currentComponent: any = ViewComponent;
|
|
140
|
+
while (hasLayout(currentComponent)) {
|
|
141
|
+
layouts.push({
|
|
142
|
+
layout: currentComponent.layout,
|
|
143
|
+
props: currentComponent.layoutProps || {},
|
|
144
|
+
});
|
|
145
|
+
currentComponent = currentComponent.layout;
|
|
146
|
+
}
|
|
135
147
|
}
|
|
136
148
|
|
|
137
|
-
// Wrap
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
149
|
+
// Wrap with each layout in REVERSE order (innermost to outermost)
|
|
150
|
+
// This produces the correct nesting: RootLayout > ControllerLayout > Page
|
|
151
|
+
// Must match server-side wrapping with data-layout and data-outlet attributes
|
|
152
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
153
|
+
const { layout: Layout, props: layoutProps } = layouts[i];
|
|
154
|
+
const layoutName = Layout.displayName || Layout.name || 'Layout';
|
|
155
|
+
result = (
|
|
156
|
+
<div data-layout={layoutName}>
|
|
157
|
+
<Layout context={context} layoutProps={layoutProps}>
|
|
158
|
+
<div data-outlet={layoutName}>{result}</div>
|
|
159
|
+
</Layout>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
141
162
|
}
|
|
142
163
|
|
|
143
164
|
return result;
|
|
144
165
|
}
|
|
145
166
|
|
|
167
|
+
// Build layouts array - use RootLayout if it exists (matching server behavior)
|
|
168
|
+
const layouts: Array<{ layout: React.ComponentType<any>; props?: any }> = [];
|
|
169
|
+
if (RootLayout) {
|
|
170
|
+
layouts.push({ layout: RootLayout, props: {} });
|
|
171
|
+
}
|
|
172
|
+
|
|
146
173
|
// Compose the component with its layout (if any)
|
|
147
|
-
const composedElement = composeWithLayout(
|
|
174
|
+
const composedElement = composeWithLayout(
|
|
175
|
+
ViewComponent,
|
|
176
|
+
initialProps,
|
|
177
|
+
renderContext,
|
|
178
|
+
layouts,
|
|
179
|
+
);
|
|
148
180
|
|
|
149
181
|
// Wrap with providers to make context and navigation state available via hooks
|
|
150
182
|
const wrappedElement = (
|
|
@@ -160,8 +192,18 @@ hydrateRoot(
|
|
|
160
192
|
<StrictMode>{wrappedElement}</StrictMode>,
|
|
161
193
|
);
|
|
162
194
|
|
|
195
|
+
// Track if initial hydration is complete to ignore false popstate events
|
|
196
|
+
let hydrationComplete = false;
|
|
197
|
+
requestAnimationFrame(() => {
|
|
198
|
+
hydrationComplete = true;
|
|
199
|
+
});
|
|
200
|
+
|
|
163
201
|
// Handle browser back/forward navigation
|
|
164
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
|
+
|
|
165
207
|
// Dynamically import navigate to avoid circular dependency with hydrate-segment
|
|
166
208
|
const { navigate } = await import('@nestjs-ssr/react/client');
|
|
167
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
|
|