@ecopages/react 0.2.0-alpha.14 → 0.2.0-alpha.15

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/CHANGELOG.md CHANGED
@@ -9,6 +9,7 @@ All notable changes to `@ecopages/react` are documented here.
9
9
  ### Bug Fixes
10
10
 
11
11
  - Fixed React hydration, Fast Refresh, module loading, doctype handling, island asset reuse, and mixed-renderer boundary resolution across Bun, Vite, and Nitro flows.
12
+ - Restored direct `ReactPlugin` construction so the exported class still accepts the public plugin options shape.
12
13
 
13
14
  ### Features
14
15
 
@@ -17,6 +18,16 @@ All notable changes to `@ecopages/react` are documented here.
17
18
  ### Refactoring
18
19
 
19
20
  - Consolidated React bundling, hydration, and runtime state behind shared service boundaries and `window.__ECO_PAGES__`.
21
+ - Moved React plugin option/default resolution into the factory and replaced renderer static config with instance-owned runtime wiring.
22
+ - Extracted React page-payload and locals serialization into a dedicated service to keep the renderer focused on orchestration.
23
+ - Centralized recursive React component-config traversal so module discovery and MDX SSR-lazy asset collection no longer reimplement graph walking.
24
+ - Moved MDX config dependency resolution out of the renderer into a dedicated React service.
25
+ - Collected shared React plugin and renderer config types into a dedicated module while keeping renderer-local runtime types close to implementation.
26
+
27
+ ### Tests
28
+
29
+ - Added Vitest browser coverage for the React `dynamic()` utility using React Testing Library.
30
+ - Added browser execution coverage for the generated React hydration bootstrap, including router ownership registration and page-root cleanup.
20
31
 
21
32
  ### Documentation
22
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/react",
3
- "version": "0.2.0-alpha.14",
3
+ "version": "0.2.0-alpha.15",
4
4
  "description": "React integration for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -53,14 +53,14 @@
53
53
  "directory": "packages/integrations/react"
54
54
  },
55
55
  "peerDependencies": {
56
- "@ecopages/core": "0.2.0-alpha.14",
56
+ "@ecopages/core": "0.2.0-alpha.15",
57
57
  "@types/react": "^19",
58
58
  "@types/react-dom": "^19",
59
59
  "react": "^19",
60
60
  "react-dom": "^19"
61
61
  },
62
62
  "dependencies": {
63
- "@ecopages/file-system": "0.2.0-alpha.14",
63
+ "@ecopages/file-system": "0.2.0-alpha.15",
64
64
  "@ecopages/logger": "^0.2.3",
65
65
  "@mdx-js/esbuild": "^3.0.1",
66
66
  "@mdx-js/mdx": "^3.1.0",
@@ -3,15 +3,23 @@
3
3
  * @module
4
4
  */
5
5
  import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPageFile, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
6
- import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/core/route-renderer/integration-renderer';
6
+ import { IntegrationRenderer, type RenderToResponseContext, type RouteModuleLoadOptions } from '@ecopages/core/route-renderer/integration-renderer';
7
7
  import type { ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
8
- import { type ReactNode } from 'react';
9
- import type { CompileOptions } from '@mdx-js/mdx';
10
- import type { ReactRouterAdapter } from './router-adapter.js';
8
+ import type { ReactNode } from 'react';
9
+ import type { ReactRendererConfig } from './react.types.js';
11
10
  import { ReactBundleService } from './services/react-bundle.service.js';
12
- import { ReactHmrPageMetadataCache } from './services/react-hmr-page-metadata-cache.js';
11
+ import { ReactMdxConfigDependencyService } from './services/react-mdx-config-dependency.service.js';
13
12
  import { ReactPageModuleService } from './services/react-page-module.service.js';
13
+ import { ReactPagePayloadService } from './services/react-page-payload.service.js';
14
14
  import { ReactHydrationAssetService } from './services/react-hydration-asset.service.js';
15
+ export type { ReactRendererConfig } from './react.types.js';
16
+ type ReactRuntimeModules = {
17
+ react: Pick<typeof import('react'), 'createElement' | 'Fragment'>;
18
+ reactDomServer: Pick<typeof import('react-dom/server'), 'renderToReadableStream' | 'renderToString'>;
19
+ };
20
+ export type ReactRendererOptions = ConstructorParameters<typeof IntegrationRenderer>[0] & {
21
+ reactConfig?: ReactRendererConfig;
22
+ };
15
23
  /**
16
24
  * Error thrown when an error occurs while rendering a React component.
17
25
  */
@@ -32,29 +40,29 @@ export declare class BundleError extends Error {
32
40
  export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
33
41
  name: string;
34
42
  componentDirectory: string;
35
- static routerAdapter: ReactRouterAdapter | undefined;
36
- static mdxCompilerOptions: CompileOptions | undefined;
37
- static mdxExtensions: string[];
38
- static hmrPageMetadataCache: ReactHmrPageMetadataCache | undefined;
43
+ private reactRuntimeModules?;
44
+ private readonly routerAdapter?;
45
+ private readonly mdxCompilerOptions?;
46
+ private readonly mdxExtensions;
47
+ private readonly hmrPageMetadataCache?;
39
48
  /**
40
49
  * Enables explicit graph behavior for React page-entry bundling.
41
50
  *
42
51
  * When true, page-entry bundles disable AST server-only stripping and rely
43
52
  * on explicit dependency declarations for browser graph composition.
44
53
  */
45
- static explicitGraphEnabled: boolean;
54
+ private readonly explicitGraphEnabled;
46
55
  /** @internal */
47
56
  readonly bundleService: ReactBundleService;
48
57
  /** @internal */
49
58
  readonly pageModuleService: ReactPageModuleService;
50
59
  /** @internal */
51
60
  readonly hydrationAssetService: ReactHydrationAssetService;
52
- constructor(options: {
53
- appConfig: ConstructorParameters<typeof IntegrationRenderer>[0]['appConfig'];
54
- assetProcessingService: ConstructorParameters<typeof IntegrationRenderer>[0]['assetProcessingService'];
55
- resolvedIntegrationDependencies?: ProcessedAsset[];
56
- runtimeOrigin: string;
57
- });
61
+ /** @internal */
62
+ readonly pagePayloadService: ReactPagePayloadService;
63
+ /** @internal */
64
+ readonly mdxConfigDependencyService: ReactMdxConfigDependencyService;
65
+ constructor(options: ReactRendererOptions);
58
66
  protected shouldRenderPageComponent(): boolean;
59
67
  /**
60
68
  * Reads the declared integration name for a component or layout.
@@ -72,14 +80,6 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
72
80
  * already selected the React integration.
73
81
  */
74
82
  private isReactManagedComponent;
75
- /**
76
- * Creates the canonical page-props payload used by router hydration.
77
- *
78
- * React pages embedded in a non-React HTML shell still need to expose the same
79
- * page-data contract as fully React-owned documents so navigation and hydration
80
- * can read one shared document payload consistently.
81
- */
82
- private buildRouterPageDataScript;
83
83
  private getRouterDocumentAttributes;
84
84
  /**
85
85
  * Commits a framework-agnostic component to React semantics.
@@ -98,14 +98,8 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
98
98
  * by React, we call it directly and require serialized HTML back.
99
99
  */
100
100
  private asNonReactShellComponent;
101
- /**
102
- * Builds the serialized page-props payload embedded into the final HTML.
103
- *
104
- * The document payload is intentionally narrower than the full server render
105
- * input: only routing data, public page props, and explicitly allowed locals are
106
- * exposed to the browser.
107
- */
108
- private buildSerializedPageProps;
101
+ protected resolveReactRuntimeModules(): ReactRuntimeModules;
102
+ private getReactRuntimeModules;
109
103
  /**
110
104
  * Appends route hydration assets for a concrete page/view file to the current
111
105
  * HTML transformer state.
@@ -133,10 +127,58 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
133
127
  * @returns Serialized component HTML with resolved child markup preserved.
134
128
  */
135
129
  private renderComponentHtml;
130
+ /**
131
+ * Restores raw child HTML that was temporarily replaced by a token during React SSR.
132
+ *
133
+ * Queued boundary resolution may render children through a fragment path before all
134
+ * nested integration tokens are resolved. When that happens, React must never see
135
+ * the resolved child HTML as a normal string child or it would escape it. The
136
+ * runtime context stores the placeholder token and the raw child HTML so the
137
+ * fragment render path can reinsert it before foreign boundary tokens are handled.
138
+ */
136
139
  private restoreRuntimeChildHtml;
140
+ /**
141
+ * Renders queued child content through React and then resolves nested boundary tokens.
142
+ *
143
+ * This path is only used for children that were deferred while React rendered the
144
+ * parent boundary. It first restores any raw child HTML placeholders owned by the
145
+ * current runtime context, then asks the shared queued-boundary resolver to swap
146
+ * foreign integration tokens with their resolved HTML.
147
+ */
137
148
  private renderQueuedChildrenToHtml;
149
+ /**
150
+ * Resolves queued renderer-owned boundary tokens produced during React component rendering.
151
+ *
152
+ * React components can enqueue nested boundaries while the parent HTML is being
153
+ * rendered. This delegates to the shared renderer-owned queue resolver but keeps
154
+ * the React-specific child rendering behavior local so raw child HTML and React's
155
+ * fragment rendering semantics stay coordinated.
156
+ */
138
157
  private resolveQueuedBoundaryHtml;
139
158
  private buildHydrationProps;
159
+ /**
160
+ * Builds the extra document props needed when React renders through a non-React HTML shell.
161
+ *
162
+ * Router-backed React pages still need to publish the canonical page-data script
163
+ * even when the outer document shell belongs to another integration.
164
+ */
165
+ private buildNonReactDocumentProps;
166
+ /**
167
+ * Renders a foreign integration component boundary that participates in React composition.
168
+ *
169
+ * Non-React components must resolve to serialized HTML so React can embed them as
170
+ * mixed-shell boundaries. Any component-owned dependencies still need to flow
171
+ * through the shared dependency resolver before queued boundary tokens are finalized.
172
+ */
173
+ private renderForeignComponentBoundary;
174
+ /**
175
+ * Renders a React-owned component boundary and attaches island hydration metadata when possible.
176
+ *
177
+ * This path keeps React-owned SSR, queued boundary resolution, and optional
178
+ * island hydration wiring together so the public `renderComponent()` method can
179
+ * read as orchestration rather than implementation detail.
180
+ */
181
+ private renderReactComponentBoundary;
140
182
  /**
141
183
  * Renders a React component for component-level orchestration.
142
184
  *
@@ -163,16 +205,8 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
163
205
  */
164
206
  isMdxFile(filePath: string): boolean;
165
207
  protected usesIntegrationPageImporter(file: string): boolean;
166
- protected importIntegrationPageFile(file: string): Promise<EcoPageFile>;
208
+ protected importIntegrationPageFile(file: string, options?: RouteModuleLoadOptions): Promise<EcoPageFile>;
167
209
  protected normalizeImportedPageFile<TPageModule extends EcoPageFile>(file: string, pageModule: TPageModule): TPageModule;
168
- /**
169
- * Processes MDX-specific configuration dependencies including layout dependencies.
170
- * @param pagePath - Absolute path to the MDX page file
171
- * @returns Processed assets for MDX configuration dependencies
172
- */
173
- private processMdxConfigDependencies;
174
- private processDeclaredMdxSsrLazyDependencies;
175
- private collectDeclaredMdxSsrLazyDependencies;
176
210
  buildRouteRenderAssets(pagePath: string): Promise<ProcessedAsset[]>;
177
211
  /**
178
212
  * Renders a full route response for the filesystem page pipeline.
@@ -184,23 +218,6 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
184
218
  */
185
219
  render({ params, query, props, locals, pageLocals, metadata, Page, Layout, HtmlTemplate, pageProps, }: IntegrationRendererRenderOptions<ReactNode>): Promise<RouteRendererBody>;
186
220
  protected getDocumentAttributes(): Record<string, string> | undefined;
187
- /**
188
- * Safely extracts the declared subset of locals for client-side hydration.
189
- *
190
- * On dynamic pages with `cache: 'dynamic'`, middleware populates `locals` with
191
- * request-scoped data (e.g., session). Only keys explicitly declared via
192
- * `Page.requires` are serialized to the client so sensitive request-only data
193
- * is not leaked into hydration payloads by default.
194
- *
195
- * On static pages, `locals` is a Proxy that throws `LocalsAccessError` on access
196
- * to prevent accidental use. This method safely detects that case and returns
197
- * `undefined` instead of throwing.
198
- *
199
- * @param locals - The locals object from the render context
200
- * @param requiredLocals - Keys explicitly requested for client hydration
201
- * @returns The filtered locals object if serializable, undefined otherwise
202
- */
203
- private getSerializableLocals;
204
221
  /**
205
222
  * Renders an arbitrary React view through the application's HTML shell.
206
223
  *