@ecopages/react 0.2.0-alpha.9 → 0.2.0-beta.0

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.
Files changed (77) hide show
  1. package/README.md +30 -13
  2. package/package.json +23 -12
  3. package/src/eco-embed.d.ts +11 -0
  4. package/src/eco-embed.js +11 -0
  5. package/src/react-hmr-strategy.d.ts +102 -18
  6. package/src/react-hmr-strategy.js +427 -50
  7. package/src/react-renderer.d.ts +100 -92
  8. package/src/react-renderer.js +356 -340
  9. package/src/react.constants.d.ts +1 -0
  10. package/src/react.constants.js +4 -0
  11. package/src/react.plugin.d.ts +25 -107
  12. package/src/react.plugin.js +109 -61
  13. package/src/react.types.d.ts +88 -0
  14. package/src/react.types.js +0 -0
  15. package/src/router-adapter.d.ts +7 -14
  16. package/src/runtime/use-sync-external-store-with-selector.d.ts +3 -0
  17. package/src/runtime/use-sync-external-store-with-selector.js +56 -0
  18. package/src/services/pages-index.d.ts +64 -0
  19. package/src/services/pages-index.js +73 -0
  20. package/src/services/react-bundle.service.d.ts +24 -9
  21. package/src/services/react-bundle.service.js +35 -24
  22. package/src/services/react-hmr-page-metadata-cache.d.ts +10 -1
  23. package/src/services/react-hmr-page-metadata-cache.js +18 -2
  24. package/src/services/react-hydration-asset.service.d.ts +28 -19
  25. package/src/services/react-hydration-asset.service.js +83 -64
  26. package/src/services/react-mdx-config-dependency.service.d.ts +36 -0
  27. package/src/services/react-mdx-config-dependency.service.js +122 -0
  28. package/src/services/react-page-module.service.d.ts +8 -3
  29. package/src/services/react-page-module.service.js +33 -26
  30. package/src/services/react-page-payload.service.d.ts +46 -0
  31. package/src/services/react-page-payload.service.js +67 -0
  32. package/src/services/react-runtime-bundle.service.d.ts +9 -2
  33. package/src/services/react-runtime-bundle.service.js +77 -16
  34. package/src/utils/client-graph-boundary-cache.d.ts +108 -0
  35. package/src/utils/client-graph-boundary-cache.js +116 -0
  36. package/src/utils/client-graph-boundary-plugin.d.ts +13 -5
  37. package/src/utils/client-graph-boundary-plugin.js +63 -5
  38. package/src/utils/component-config-traversal.d.ts +36 -0
  39. package/src/utils/component-config-traversal.js +54 -0
  40. package/src/utils/declared-modules.d.ts +1 -1
  41. package/src/utils/declared-modules.js +7 -16
  42. package/src/utils/dynamic.test.browser.d.ts +1 -0
  43. package/src/utils/dynamic.test.browser.js +33 -0
  44. package/src/utils/hydration-scripts.d.ts +9 -5
  45. package/src/utils/hydration-scripts.js +119 -34
  46. package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
  47. package/src/utils/hydration-scripts.test.browser.js +198 -0
  48. package/src/utils/react-dom-runtime-interop-plugin.d.ts +1 -1
  49. package/src/utils/react-dom-runtime-interop-plugin.js +9 -0
  50. package/src/utils/react-mdx-loader-plugin.d.ts +1 -1
  51. package/src/utils/{react-runtime-specifier-map.d.ts → react-runtime-alias-map.d.ts} +3 -1
  52. package/src/utils/react-runtime-alias-map.js +90 -0
  53. package/CHANGELOG.md +0 -27
  54. package/src/react-hmr-strategy.ts +0 -386
  55. package/src/react-renderer.ts +0 -803
  56. package/src/react.plugin.ts +0 -276
  57. package/src/router-adapter.ts +0 -95
  58. package/src/services/react-bundle.service.ts +0 -108
  59. package/src/services/react-hmr-page-metadata-cache.ts +0 -24
  60. package/src/services/react-hydration-asset.service.ts +0 -263
  61. package/src/services/react-page-module.service.ts +0 -224
  62. package/src/services/react-runtime-bundle.service.ts +0 -172
  63. package/src/utils/client-graph-boundary-plugin.ts +0 -831
  64. package/src/utils/client-only.ts +0 -27
  65. package/src/utils/declared-modules.ts +0 -99
  66. package/src/utils/dynamic.ts +0 -27
  67. package/src/utils/hmr-scripts.ts +0 -47
  68. package/src/utils/html-boundary.ts +0 -66
  69. package/src/utils/hydration-scripts.ts +0 -459
  70. package/src/utils/reachability-analyzer.ts +0 -593
  71. package/src/utils/react-dom-runtime-interop-plugin.ts +0 -33
  72. package/src/utils/react-mdx-loader-plugin.ts +0 -63
  73. package/src/utils/react-runtime-specifier-map.js +0 -37
  74. package/src/utils/react-runtime-specifier-map.ts +0 -45
  75. package/src/utils/use-sync-external-store-shim-plugin.d.ts +0 -5
  76. package/src/utils/use-sync-external-store-shim-plugin.js +0 -41
  77. package/src/utils/use-sync-external-store-shim-plugin.ts +0 -45
@@ -2,20 +2,23 @@
2
2
  * This module contains the React renderer
3
3
  * @module
4
4
  */
5
- import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoComponentConfig, EcoPageFile, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
6
- import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/core/route-renderer/integration-renderer';
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';
5
+ import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPageFile, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
6
+ import { IntegrationRenderer, type HtmlDocumentContribution, type HtmlDocumentContributionContext, type PageBrowserGraphContributionContext, type RenderToResponseContext, type RouteModuleLoadOptions } from '@ecopages/core/route-renderer/integration-renderer';
7
+ import type { AssetDefinition, ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
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
- type ReactPageModule = EcoPageFile<{
16
- config?: EcoComponentConfig;
17
- }> & {
18
- config?: EcoComponentConfig;
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;
19
22
  };
20
23
  /**
21
24
  * Error thrown when an error occurs while rendering a React component.
@@ -37,29 +40,29 @@ export declare class BundleError extends Error {
37
40
  export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
38
41
  name: string;
39
42
  componentDirectory: string;
40
- static routerAdapter: ReactRouterAdapter | undefined;
41
- static mdxCompilerOptions: CompileOptions | undefined;
42
- static mdxExtensions: string[];
43
- static hmrPageMetadataCache: ReactHmrPageMetadataCache | undefined;
43
+ private reactRuntimeModules?;
44
+ private readonly routerAdapter?;
45
+ private readonly mdxCompilerOptions?;
46
+ private readonly mdxExtensions;
47
+ private readonly hmrPageMetadataCache?;
44
48
  /**
45
49
  * Enables explicit graph behavior for React page-entry bundling.
46
50
  *
47
51
  * When true, page-entry bundles disable AST server-only stripping and rely
48
52
  * on explicit dependency declarations for browser graph composition.
49
53
  */
50
- static explicitGraphEnabled: boolean;
54
+ private readonly explicitGraphEnabled;
51
55
  /** @internal */
52
56
  readonly bundleService: ReactBundleService;
53
57
  /** @internal */
54
58
  readonly pageModuleService: ReactPageModuleService;
55
59
  /** @internal */
56
60
  readonly hydrationAssetService: ReactHydrationAssetService;
57
- constructor(options: {
58
- appConfig: ConstructorParameters<typeof IntegrationRenderer>[0]['appConfig'];
59
- assetProcessingService: ConstructorParameters<typeof IntegrationRenderer>[0]['assetProcessingService'];
60
- resolvedIntegrationDependencies?: ProcessedAsset[];
61
- runtimeOrigin: string;
62
- });
61
+ /** @internal */
62
+ readonly pagePayloadService: ReactPagePayloadService;
63
+ /** @internal */
64
+ readonly mdxConfigDependencyService: ReactMdxConfigDependencyService;
65
+ constructor(options: ReactRendererOptions);
63
66
  protected shouldRenderPageComponent(): boolean;
64
67
  /**
65
68
  * Reads the declared integration name for a component or layout.
@@ -77,14 +80,7 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
77
80
  * already selected the React integration.
78
81
  */
79
82
  private isReactManagedComponent;
80
- /**
81
- * Creates the canonical page-props payload used by router hydration.
82
- *
83
- * React pages embedded in a non-React HTML shell still need to expose the same
84
- * page-data contract as fully React-owned documents so navigation and hydration
85
- * can read one marker consistently.
86
- */
87
- private buildRouterPageDataScript;
83
+ private getComponentRequires;
88
84
  private getRouterDocumentAttributes;
89
85
  /**
90
86
  * Commits a framework-agnostic component to React semantics.
@@ -103,27 +99,13 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
103
99
  * by React, we call it directly and require serialized HTML back.
104
100
  */
105
101
  private asNonReactShellComponent;
106
- /**
107
- * Builds the serialized page-props payload embedded into the final HTML.
108
- *
109
- * The document payload is intentionally narrower than the full server render
110
- * input: only routing data, public page props, and explicitly allowed locals are
111
- * exposed to the browser.
112
- */
113
- private buildSerializedPageProps;
102
+ protected resolveReactRuntimeModules(): ReactRuntimeModules;
103
+ private getReactRuntimeModules;
114
104
  /**
115
105
  * Appends route hydration assets for a concrete page/view file to the current
116
106
  * HTML transformer state.
117
107
  */
118
108
  private appendHydrationAssetsForFile;
119
- /**
120
- * Resolves metadata for direct `renderToResponse()` calls.
121
- *
122
- * View rendering bypasses the normal route-file pipeline, so metadata has to be
123
- * evaluated here from either the component-level generator or the application
124
- * default.
125
- */
126
- private resolveViewMetadata;
127
109
  /**
128
110
  * Renders a non-React layout or HTML template and enforces that mixed shells
129
111
  * return serialized HTML.
@@ -133,21 +115,69 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
133
115
  */
134
116
  private renderNonReactShellComponent;
135
117
  /**
136
- * Produces the page body before the final HTML template is applied.
118
+ * Renders one React component while preserving already-resolved child HTML.
119
+ *
120
+ * When nested foreign-subtree resolution has already produced child HTML for this
121
+ * component, the child payload must remain raw SSR output rather than a React
122
+ * string child, otherwise React would escape it. This helper renders a unique
123
+ * token through React and swaps that token back to the resolved HTML
124
+ * afterward.
125
+ *
126
+ * @param input Component render input for the current render step.
127
+ * @param context React-specific render context for stable token generation.
128
+ * @returns Serialized component HTML with resolved child markup preserved.
129
+ */
130
+ private renderComponentHtml;
131
+ /**
132
+ * Restores raw child HTML that was temporarily replaced by a token during React SSR.
133
+ *
134
+ * Queued foreign-subtree resolution may render children through a fragment path before all
135
+ * nested integration tokens are resolved. When that happens, React must never see
136
+ * the resolved child HTML as a normal string child or it would escape it. The
137
+ * runtime context stores the placeholder token and the raw child HTML so the
138
+ * fragment render path can reinsert it before foreign-subtree tokens are handled.
139
+ */
140
+ private restoreRuntimeChildHtml;
141
+ /**
142
+ * Renders queued child content through React and then resolves nested foreign-subtree tokens.
137
143
  *
138
- * This method owns the React/non-React layout split. React-managed layouts stay
139
- * as React elements so they can stream normally; non-React layouts are rendered
140
- * to HTML first and then passed through as serialized content.
144
+ * This path is only used for children that were deferred while React rendered the
145
+ * parent component. It first restores any raw child HTML placeholders owned by the
146
+ * current runtime context, then asks the shared queued foreign-subtree resolver to swap
147
+ * foreign integration tokens with their resolved HTML.
141
148
  */
142
- private composePageContent;
149
+ private renderQueuedChildrenToHtml;
143
150
  /**
144
- * Wraps composed page content in the final document template.
151
+ * Resolves queued renderer-owned foreign-subtree tokens produced during React component rendering.
145
152
  *
146
- * React-owned HTML templates stream directly. Non-React templates receive
147
- * pre-rendered page HTML plus the canonical React page-data payload so the
148
- * client runtime can recover page data after cross-integration handoff.
153
+ * React components can enqueue nested foreign subtrees while the parent HTML is being
154
+ * rendered. This delegates to the shared renderer-owned queue resolver but keeps
155
+ * the React-specific child rendering behavior local so raw child HTML and React's
156
+ * fragment rendering semantics stay coordinated.
149
157
  */
150
- private renderDocument;
158
+ private resolveQueuedForeignSubtreeHtml;
159
+ private buildHydrationProps;
160
+ /**
161
+ * Builds shared document html contributions for router-backed React pages rendered
162
+ * through a non-React HTML shell.
163
+ */
164
+ private buildNonReactDocumentContributions;
165
+ /**
166
+ * Renders a foreign integration component that participates in React composition.
167
+ *
168
+ * Non-React components must resolve to serialized HTML so React can embed them as
169
+ * mixed-shell children. Any component-owned dependencies still need to flow
170
+ * through the shared dependency resolver before queued foreign-subtree tokens are finalized.
171
+ */
172
+ private renderForeignComponentWithSerializedHtml;
173
+ /**
174
+ * Renders a React-owned component and attaches island hydration metadata when possible.
175
+ *
176
+ * This path keeps React-owned SSR, queued foreign-subtree resolution, and optional
177
+ * island hydration wiring together so the public `renderComponent()` method can
178
+ * read as orchestration rather than implementation detail.
179
+ */
180
+ private renderReactManagedComponent;
151
181
  /**
152
182
  * Renders a React component for component-level orchestration.
153
183
  *
@@ -156,35 +186,30 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
156
186
  * - When an explicit component instance id is provided, a stable
157
187
  * `data-eco-component-id` attribute is attached so island hydration can target it.
158
188
  * - Without an explicit instance id, component renders remain plain SSR output.
189
+ * - When resolved child HTML is provided, that foreign subtree is treated as a pure SSR
190
+ * composition step and does not emit hydration assets for the parent wrapper.
159
191
  *
160
192
  * This preserves DOM shape for global CSS/layout selectors while keeping a
161
193
  * deterministic mount target per component instance.
162
194
  */
163
195
  renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
196
+ protected createForeignChildRuntime(options: {
197
+ renderInput: ComponentRenderInput;
198
+ rendererCache: Map<string, IntegrationRenderer<any>>;
199
+ }): import("@ecopages/core").ForeignChildRuntime;
164
200
  /**
165
201
  * Checks if the given file path corresponds to an MDX file based on configured extensions.
166
202
  * @param filePath - The file path to check
167
203
  * @returns True if the file is an MDX file
168
204
  */
169
205
  isMdxFile(filePath: string): boolean;
170
- /**
171
- * Processes MDX-specific configuration dependencies including layout dependencies.
172
- * @param pagePath - Absolute path to the MDX page file
173
- * @returns Processed assets for MDX configuration dependencies
174
- */
175
- private processMdxConfigDependencies;
176
- private processDeclaredMdxSsrLazyDependencies;
177
- private collectDeclaredMdxSsrLazyDependencies;
178
- buildRouteRenderAssets(pagePath: string): Promise<ProcessedAsset[]>;
179
- /**
180
- * Imports a page module while normalizing React MDX modules to the same shape
181
- * as ordinary React page files.
182
- *
183
- * MDX page imports can expose `config` separately from the default export. The
184
- * React renderer reattaches that config to the page component so downstream
185
- * layout, dependency, and hydration logic can treat MDX and TSX pages the same.
186
- */
187
- protected importPageFile(file: string): Promise<ReactPageModule>;
206
+ protected usesIntegrationPageImporter(file: string): boolean;
207
+ protected importIntegrationPageFile(file: string, options?: RouteModuleLoadOptions): Promise<EcoPageFile>;
208
+ protected normalizeImportedPageFile<TPageModule extends EcoPageFile>(file: string, pageModule: TPageModule): TPageModule;
209
+ protected collectPageBrowserGraphContribution(context: PageBrowserGraphContributionContext): Promise<{
210
+ dependencies?: AssetDefinition[];
211
+ assets?: ProcessedAsset[];
212
+ }>;
188
213
  /**
189
214
  * Renders a full route response for the filesystem page pipeline.
190
215
  *
@@ -194,24 +219,8 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
194
219
  * React shell tree, and hand the result back as a document body.
195
220
  */
196
221
  render({ params, query, props, locals, pageLocals, metadata, Page, Layout, HtmlTemplate, pageProps, }: IntegrationRendererRenderOptions<ReactNode>): Promise<RouteRendererBody>;
222
+ protected getHtmlDocumentContributions(options: HtmlDocumentContributionContext<ReactNode>): HtmlDocumentContribution[] | undefined;
197
223
  protected getDocumentAttributes(): Record<string, string> | undefined;
198
- /**
199
- * Safely extracts the declared subset of locals for client-side hydration.
200
- *
201
- * On dynamic pages with `cache: 'dynamic'`, middleware populates `locals` with
202
- * request-scoped data (e.g., session). Only keys explicitly declared via
203
- * `Page.requires` are serialized to the client so sensitive request-only data
204
- * is not leaked into hydration payloads by default.
205
- *
206
- * On static pages, `locals` is a Proxy that throws `LocalsAccessError` on access
207
- * to prevent accidental use. This method safely detects that case and returns
208
- * `undefined` instead of throwing.
209
- *
210
- * @param locals - The locals object from the render context
211
- * @param requiredLocals - Keys explicitly requested for client hydration
212
- * @returns The filtered locals object if serializable, undefined otherwise
213
- */
214
- private getSerializableLocals;
215
224
  /**
216
225
  * Renders an arbitrary React view through the application's HTML shell.
217
226
  *
@@ -222,4 +231,3 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
222
231
  */
223
232
  renderToResponse<P = Record<string, unknown>>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
224
233
  }
225
- export {};