@nestjs-ssr/react 0.3.2 → 0.3.4

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.
@@ -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
@@ -111,40 +121,53 @@ function hasLayout(
111
121
  function composeWithLayout(
112
122
  ViewComponent: React.ComponentType<any>,
113
123
  props: any,
124
+ context?: any,
125
+ layouts: Array<{ layout: React.ComponentType<any>; props?: any }> = [],
114
126
  ): React.ReactElement {
115
- const element = <ViewComponent {...props} />;
116
-
117
- // Check if component has a layout
118
- if (!hasLayout(ViewComponent)) {
119
- return element;
120
- }
121
-
122
- // Collect all layouts in the chain (innermost to outermost)
123
- const layoutChain: Array<{
124
- Layout: React.ComponentType<any>;
125
- layoutProps: any;
126
- }> = [];
127
- let currentComponent: any = ViewComponent;
128
-
129
- while (hasLayout(currentComponent)) {
130
- layoutChain.push({
131
- Layout: currentComponent.layout,
132
- layoutProps: currentComponent.layoutProps || {},
133
- });
134
- currentComponent = currentComponent.layout;
127
+ // Start with the page component
128
+ let result = <ViewComponent {...props} />;
129
+
130
+ // If no layouts passed, check if component has its own layout chain
131
+ if (layouts.length === 0 && hasLayout(ViewComponent)) {
132
+ let currentComponent: any = ViewComponent;
133
+ while (hasLayout(currentComponent)) {
134
+ layouts.push({
135
+ layout: currentComponent.layout,
136
+ props: currentComponent.layoutProps || {},
137
+ });
138
+ currentComponent = currentComponent.layout;
139
+ }
135
140
  }
136
141
 
137
- // Wrap the element with layouts from innermost to outermost
138
- let result = element;
139
- for (const { Layout, layoutProps } of layoutChain) {
140
- result = <Layout layoutProps={layoutProps}>{result}</Layout>;
142
+ // Wrap with each layout in the chain
143
+ // Must match server-side wrapping with data-layout and data-outlet attributes
144
+ for (const { layout: Layout, props: layoutProps } of layouts) {
145
+ const layoutName = Layout.displayName || Layout.name || 'Layout';
146
+ result = (
147
+ <div data-layout={layoutName}>
148
+ <Layout context={context} layoutProps={layoutProps}>
149
+ <div data-outlet={layoutName}>{result}</div>
150
+ </Layout>
151
+ </div>
152
+ );
141
153
  }
142
154
 
143
155
  return result;
144
156
  }
145
157
 
158
+ // Build layouts array - use RootLayout if it exists (matching server behavior)
159
+ const layouts: Array<{ layout: React.ComponentType<any>; props?: any }> = [];
160
+ if (RootLayout) {
161
+ layouts.push({ layout: RootLayout, props: {} });
162
+ }
163
+
146
164
  // Compose the component with its layout (if any)
147
- const composedElement = composeWithLayout(ViewComponent, initialProps);
165
+ const composedElement = composeWithLayout(
166
+ ViewComponent,
167
+ initialProps,
168
+ renderContext,
169
+ layouts,
170
+ );
148
171
 
149
172
  // Wrap with providers to make context and navigation state available via hooks
150
173
  const wrappedElement = (
@@ -2,6 +2,24 @@ import React from 'react';
2
2
  import { renderToString, renderToPipeableStream } from 'react-dom/server';
3
3
  import { PageContextProvider } from '@nestjs-ssr/react/client';
4
4
 
5
+ // Auto-discover root layout using Vite's glob import
6
+ // This eagerly loads layout if it exists, null otherwise
7
+ // @ts-ignore - Vite-specific API
8
+ const layoutModules = import.meta.glob('@/views/layout.tsx', {
9
+ eager: true,
10
+ }) as Record<string, { default: React.ComponentType<any> }>;
11
+
12
+ const layoutPath = Object.keys(layoutModules)[0];
13
+ const RootLayout = layoutPath ? layoutModules[layoutPath].default : null;
14
+
15
+ /**
16
+ * Get the root layout component.
17
+ * Used by RenderService in production when dynamic import isn't available.
18
+ */
19
+ export function getRootLayout(): React.ComponentType<any> | null {
20
+ return RootLayout;
21
+ }
22
+
5
23
  /**
6
24
  * Compose a component with its layouts from the interceptor.
7
25
  * Layouts are passed from the RenderInterceptor based on decorators.