@ecopages/react 0.2.0-alpha.5 → 0.2.0-alpha.50

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 (68) hide show
  1. package/README.md +152 -29
  2. package/package.json +16 -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 +60 -43
  6. package/src/react-hmr-strategy.js +297 -144
  7. package/src/react-renderer.d.ts +169 -42
  8. package/src/react-renderer.js +484 -164
  9. package/src/react.constants.d.ts +1 -0
  10. package/src/react.constants.js +4 -0
  11. package/src/react.plugin.d.ts +38 -111
  12. package/src/react.plugin.js +132 -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/services/react-bundle.service.d.ts +19 -31
  17. package/src/services/react-bundle.service.js +51 -100
  18. package/src/services/react-hmr-page-metadata-cache.d.ts +9 -0
  19. package/src/services/react-hmr-page-metadata-cache.js +18 -2
  20. package/src/services/react-hydration-asset.service.d.ts +28 -19
  21. package/src/services/react-hydration-asset.service.js +85 -66
  22. package/src/services/react-mdx-config-dependency.service.d.ts +36 -0
  23. package/src/services/react-mdx-config-dependency.service.js +122 -0
  24. package/src/services/react-page-module.service.d.ts +10 -2
  25. package/src/services/react-page-module.service.js +47 -39
  26. package/src/services/react-page-payload.service.d.ts +46 -0
  27. package/src/services/react-page-payload.service.js +67 -0
  28. package/src/services/react-runtime-bundle.service.d.ts +15 -13
  29. package/src/services/react-runtime-bundle.service.js +103 -180
  30. package/src/utils/client-graph-boundary-plugin.d.ts +1 -1
  31. package/src/utils/client-graph-boundary-plugin.js +80 -3
  32. package/src/utils/component-config-traversal.d.ts +36 -0
  33. package/src/utils/component-config-traversal.js +54 -0
  34. package/src/utils/declared-modules.d.ts +1 -1
  35. package/src/utils/declared-modules.js +7 -16
  36. package/src/utils/dynamic.test.browser.d.ts +1 -0
  37. package/src/utils/dynamic.test.browser.js +33 -0
  38. package/src/utils/hydration-scripts.d.ts +27 -6
  39. package/src/utils/hydration-scripts.js +177 -44
  40. package/src/utils/hydration-scripts.test.browser.d.ts +1 -0
  41. package/src/utils/hydration-scripts.test.browser.js +198 -0
  42. package/src/utils/react-dom-runtime-interop-plugin.d.ts +5 -0
  43. package/src/utils/react-dom-runtime-interop-plugin.js +29 -0
  44. package/src/utils/react-mdx-loader-plugin.d.ts +1 -1
  45. package/src/utils/react-mdx-loader-plugin.js +13 -5
  46. package/src/utils/react-runtime-alias-map.d.ts +6 -0
  47. package/src/utils/react-runtime-alias-map.js +33 -0
  48. package/src/utils/use-sync-external-store-shim-plugin.d.ts +5 -0
  49. package/src/utils/use-sync-external-store-shim-plugin.js +41 -0
  50. package/CHANGELOG.md +0 -67
  51. package/src/react-hmr-strategy.ts +0 -455
  52. package/src/react-renderer.ts +0 -403
  53. package/src/react.plugin.ts +0 -241
  54. package/src/router-adapter.ts +0 -95
  55. package/src/services/react-bundle.service.ts +0 -217
  56. package/src/services/react-hmr-page-metadata-cache.ts +0 -24
  57. package/src/services/react-hydration-asset.service.ts +0 -260
  58. package/src/services/react-page-module.service.ts +0 -214
  59. package/src/services/react-runtime-bundle.service.ts +0 -271
  60. package/src/utils/client-graph-boundary-plugin.ts +0 -710
  61. package/src/utils/client-only.ts +0 -27
  62. package/src/utils/declared-modules.ts +0 -99
  63. package/src/utils/dynamic.ts +0 -27
  64. package/src/utils/hmr-scripts.ts +0 -47
  65. package/src/utils/html-boundary.ts +0 -66
  66. package/src/utils/hydration-scripts.ts +0 -338
  67. package/src/utils/reachability-analyzer.ts +0 -593
  68. package/src/utils/react-mdx-loader-plugin.ts +0 -40
@@ -2,16 +2,24 @@
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
+ 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,75 +40,194 @@ export declare class BundleError extends Error {
32
40
  export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
33
41
  name: string;
34
42
  componentDirectory: string;
35
- private componentRenderSequence;
36
- static routerAdapter: ReactRouterAdapter | undefined;
37
- static mdxCompilerOptions: CompileOptions | undefined;
38
- static mdxExtensions: string[];
39
- static hmrPageMetadataCache: ReactHmrPageMetadataCache | undefined;
43
+ private reactRuntimeModules?;
44
+ private readonly routerAdapter?;
45
+ private readonly mdxCompilerOptions?;
46
+ private readonly mdxExtensions;
47
+ private readonly hmrPageMetadataCache?;
40
48
  /**
41
49
  * Enables explicit graph behavior for React page-entry bundling.
42
50
  *
43
51
  * When true, page-entry bundles disable AST server-only stripping and rely
44
52
  * on explicit dependency declarations for browser graph composition.
45
53
  */
46
- static explicitGraphEnabled: boolean;
54
+ private readonly explicitGraphEnabled;
47
55
  /** @internal */
48
56
  readonly bundleService: ReactBundleService;
49
57
  /** @internal */
50
58
  readonly pageModuleService: ReactPageModuleService;
51
59
  /** @internal */
52
60
  readonly hydrationAssetService: ReactHydrationAssetService;
53
- constructor(options: {
54
- appConfig: ConstructorParameters<typeof IntegrationRenderer>[0]['appConfig'];
55
- assetProcessingService: ConstructorParameters<typeof IntegrationRenderer>[0]['assetProcessingService'];
56
- resolvedIntegrationDependencies?: ProcessedAsset[];
57
- runtimeOrigin: string;
58
- });
61
+ /** @internal */
62
+ readonly pagePayloadService: ReactPagePayloadService;
63
+ /** @internal */
64
+ readonly mdxConfigDependencyService: ReactMdxConfigDependencyService;
65
+ constructor(options: ReactRendererOptions);
59
66
  protected shouldRenderPageComponent(): boolean;
67
+ /**
68
+ * Reads the declared integration name for a component or layout.
69
+ *
70
+ * We honor both the explicit `config.integration` override and injected
71
+ * `config.__eco.integration` metadata because pages can arrive here through
72
+ * authored config as well as build-time component metadata.
73
+ */
74
+ private getComponentIntegration;
75
+ /**
76
+ * Returns whether a component should stay inside the React render lane.
77
+ *
78
+ * Components without explicit integration metadata are treated as React-owned
79
+ * here because this renderer only receives them after the route pipeline has
80
+ * already selected the React integration.
81
+ */
82
+ private isReactManagedComponent;
83
+ private getComponentRequires;
84
+ private getRouterDocumentAttributes;
85
+ /**
86
+ * Commits a framework-agnostic component to React semantics.
87
+ *
88
+ * This is one of the two real cast boundaries in this file. Core keeps
89
+ * `EcoComponent` broad so integrations can share the same public surface; once
90
+ * the React renderer is executing, `createElement()` needs a concrete React
91
+ * component signature.
92
+ */
93
+ private asReactComponent;
94
+ /**
95
+ * Commits a mixed-shell component to the string-returning contract required by
96
+ * non-React layouts and HTML templates.
97
+ *
98
+ * This is the second real cast boundary: once we decide a shell is not managed
99
+ * by React, we call it directly and require serialized HTML back.
100
+ */
101
+ private asNonReactShellComponent;
102
+ protected resolveReactRuntimeModules(): ReactRuntimeModules;
103
+ private getReactRuntimeModules;
104
+ /**
105
+ * Appends route hydration assets for a concrete page/view file to the current
106
+ * HTML transformer state.
107
+ */
108
+ private appendHydrationAssetsForFile;
109
+ /**
110
+ * Renders a non-React layout or HTML template and enforces that mixed shells
111
+ * return serialized HTML.
112
+ *
113
+ * The React renderer can compose through another integration's shell, but only
114
+ * if that shell yields a string that can be inserted into the final document.
115
+ */
116
+ private renderNonReactShellComponent;
117
+ /**
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.
143
+ *
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.
148
+ */
149
+ private renderQueuedChildrenToHtml;
150
+ /**
151
+ * Resolves queued renderer-owned foreign-subtree tokens produced during React component rendering.
152
+ *
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.
157
+ */
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;
60
181
  /**
61
182
  * Renders a React component for component-level orchestration.
62
183
  *
63
184
  * Behavior:
64
185
  * - SSR always returns the component's own root HTML (no synthetic wrapper).
65
- * - For single-root output, a stable `data-eco-component-id` attribute is attached
66
- * to the root element so the client island runtime can target it directly.
67
- * - Island client scripts are emitted through `assets` and mounted independently.
186
+ * - When an explicit component instance id is provided, a stable
187
+ * `data-eco-component-id` attribute is attached so island hydration can target it.
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.
68
191
  *
69
192
  * This preserves DOM shape for global CSS/layout selectors while keeping a
70
193
  * deterministic mount target per component instance.
71
194
  */
72
195
  renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
196
+ protected createForeignChildRuntime(options: {
197
+ renderInput: ComponentRenderInput;
198
+ rendererCache: Map<string, IntegrationRenderer<any>>;
199
+ }): import("@ecopages/core").ForeignChildRuntime;
73
200
  /**
74
201
  * Checks if the given file path corresponds to an MDX file based on configured extensions.
75
202
  * @param filePath - The file path to check
76
203
  * @returns True if the file is an MDX file
77
204
  */
78
205
  isMdxFile(filePath: string): boolean;
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
+ }>;
79
213
  /**
80
- * Processes MDX-specific configuration dependencies including layout dependencies.
81
- * @param pagePath - Absolute path to the MDX page file
82
- * @returns Processed assets for MDX configuration dependencies
214
+ * Renders a full route response for the filesystem page pipeline.
215
+ *
216
+ * This path receives already-resolved route metadata, layout, locals, and HTML
217
+ * template instances from the shared renderer orchestration. Its main job is to
218
+ * serialize only the browser-safe page payload, compose the mixed React/non-
219
+ * React shell tree, and hand the result back as a document body.
83
220
  */
84
- private processMdxConfigDependencies;
85
- buildRouteRenderAssets(pagePath: string): Promise<ProcessedAsset[]>;
86
- protected importPageFile(file: string): Promise<EcoPageFile<{
87
- config?: EcoComponentConfig;
88
- }>>;
89
221
  render({ params, query, props, locals, pageLocals, metadata, Page, Layout, HtmlTemplate, pageProps, }: IntegrationRendererRenderOptions<ReactNode>): Promise<RouteRendererBody>;
222
+ protected getHtmlDocumentContributions(options: HtmlDocumentContributionContext<ReactNode>): HtmlDocumentContribution[] | undefined;
223
+ protected getDocumentAttributes(): Record<string, string> | undefined;
90
224
  /**
91
- * Safely extracts locals for client-side hydration.
92
- *
93
- * On dynamic pages with `cache: 'dynamic'`, middleware populates `locals` with
94
- * request-scoped data (e.g., session). This data needs to be serialized to the
95
- * client for hydration to match the server-rendered output.
96
- *
97
- * On static pages, `locals` is a Proxy that throws `LocalsAccessError` on access
98
- * to prevent accidental use. This method safely detects that case and returns
99
- * `undefined` instead of throwing.
225
+ * Renders an arbitrary React view through the application's HTML shell.
100
226
  *
101
- * @param locals - The locals object from the render context
102
- * @returns The locals object if serializable, undefined otherwise
227
+ * Unlike route rendering, this path starts from a single component rather than a
228
+ * page module discovered by the router. It still needs to resolve metadata,
229
+ * layout dependencies, and hydration assets so direct `ctx.render()` calls match
230
+ * normal page responses.
103
231
  */
104
- private getSerializableLocals;
105
232
  renderToResponse<P = Record<string, unknown>>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
106
233
  }