@ecopages/react 0.2.0-alpha.39 → 0.2.0-alpha.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/react",
3
- "version": "0.2.0-alpha.39",
3
+ "version": "0.2.0-alpha.40",
4
4
  "description": "React integration for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -76,9 +76,5 @@
76
76
  "oxc-transform": "^0.124.0",
77
77
  "source-map": "^0.7.6",
78
78
  "vfile": "^6.0.3"
79
- },
80
- "overrides": {
81
- "react": "^19",
82
- "react-dom": "^19"
83
79
  }
84
80
  }
@@ -3,8 +3,8 @@
3
3
  * @module
4
4
  */
5
5
  import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPageFile, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
6
- import { IntegrationRenderer, type RenderToResponseContext, type RouteModuleLoadOptions } from '@ecopages/core/route-renderer/integration-renderer';
7
- import type { ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
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
8
  import type { ReactNode } from 'react';
9
9
  import type { ReactRendererConfig } from './react.types.js';
10
10
  import { ReactBundleService } from './services/react-bundle.service.js';
@@ -158,12 +158,10 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
158
158
  private resolveQueuedForeignSubtreeHtml;
159
159
  private buildHydrationProps;
160
160
  /**
161
- * Builds the extra document props needed when React renders through a non-React HTML shell.
162
- *
163
- * Router-backed React pages still need to publish the canonical page-data script
164
- * even when the outer document shell belongs to another integration.
161
+ * Builds shared document html contributions for router-backed React pages rendered
162
+ * through a non-React HTML shell.
165
163
  */
166
- private buildNonReactDocumentProps;
164
+ private buildNonReactDocumentContributions;
167
165
  /**
168
166
  * Renders a foreign integration component that participates in React composition.
169
167
  *
@@ -208,8 +206,9 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
208
206
  protected usesIntegrationPageImporter(file: string): boolean;
209
207
  protected importIntegrationPageFile(file: string, options?: RouteModuleLoadOptions): Promise<EcoPageFile>;
210
208
  protected normalizeImportedPageFile<TPageModule extends EcoPageFile>(file: string, pageModule: TPageModule): TPageModule;
211
- buildPageBrowserGraph(pagePath: string): Promise<{
212
- assets: ProcessedAsset[];
209
+ protected collectPageBrowserGraphContribution(context: PageBrowserGraphContributionContext): Promise<{
210
+ dependencies?: AssetDefinition[];
211
+ assets?: ProcessedAsset[];
213
212
  }>;
214
213
  /**
215
214
  * Renders a full route response for the filesystem page pipeline.
@@ -220,6 +219,7 @@ export declare class ReactRenderer extends IntegrationRenderer<ReactNode> {
220
219
  * React shell tree, and hand the result back as a document body.
221
220
  */
222
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
223
  protected getDocumentAttributes(): Record<string, string> | undefined;
224
224
  /**
225
225
  * Renders an arbitrary React view through the application's HTML shell.
@@ -178,8 +178,8 @@ class ReactRenderer extends IntegrationRenderer {
178
178
  if (!filePath) {
179
179
  return;
180
180
  }
181
- const pageBrowserGraph = await this.buildPageBrowserGraph(filePath);
182
- this.appendProcessedDependencies(pageBrowserGraph.assets);
181
+ const pageBrowserGraph = await this.resolvePageBrowserGraphForFile(filePath);
182
+ this.mergePageBrowserGraphIntoPagePackage(pageBrowserGraph);
183
183
  }
184
184
  /**
185
185
  * Renders a non-React layout or HTML template and enforces that mixed shells
@@ -304,18 +304,19 @@ class ReactRenderer extends IntegrationRenderer {
304
304
  return hydrationProps;
305
305
  }
306
306
  /**
307
- * Builds the extra document props needed when React renders through a non-React HTML shell.
308
- *
309
- * Router-backed React pages still need to publish the canonical page-data script
310
- * even when the outer document shell belongs to another integration.
307
+ * Builds shared document html contributions for router-backed React pages rendered
308
+ * through a non-React HTML shell.
311
309
  */
312
- buildNonReactDocumentProps(htmlTemplate, pageProps) {
310
+ buildNonReactDocumentContributions(htmlTemplate, pageProps) {
313
311
  if (this.isReactManagedComponent(htmlTemplate) || !this.routerAdapter) {
314
312
  return void 0;
315
313
  }
316
- return {
317
- headContent: this.pagePayloadService.buildRouterPageDataScript(pageProps)
318
- };
314
+ return [
315
+ {
316
+ placement: "head-append",
317
+ html: this.pagePayloadService.buildRouterPageDataScript(pageProps)
318
+ }
319
+ ];
319
320
  }
320
321
  /**
321
322
  * Renders a foreign integration component that participates in React composition.
@@ -458,29 +459,30 @@ class ReactRenderer extends IntegrationRenderer {
458
459
  config
459
460
  };
460
461
  }
461
- async buildPageBrowserGraph(pagePath) {
462
+ async collectPageBrowserGraphContribution(context) {
462
463
  try {
463
- const pageModule = await this.importPageFile(pagePath);
464
+ const { file: pagePath, pageModule } = context;
464
465
  const shouldHydrate = this.explicitGraphEnabled ? true : this.pageModuleService.shouldHydratePage(pageModule);
465
466
  if (!shouldHydrate) {
466
467
  return { assets: [] };
467
468
  }
468
469
  const isMdx = this.pageModuleService.isMdxFile(pagePath);
469
470
  const declaredModules = this.pageModuleService.collectPageDeclaredModules(pageModule);
470
- const processedAssets = await this.hydrationAssetService.buildPageBrowserGraphAssets(
471
+ const dependencies = await this.hydrationAssetService.createPageBrowserGraphDependencies(
471
472
  pagePath,
472
473
  isMdx,
473
474
  declaredModules
474
475
  );
476
+ const assets = [];
475
477
  if (isMdx) {
476
478
  const mdxConfigAssets = await this.mdxConfigDependencyService.processMdxConfigDependencies({
477
479
  pagePath,
478
480
  config: pageModule.config,
479
481
  processComponentDependencies: async (components) => await this.processComponentDependencies(components)
480
482
  });
481
- return { assets: [...processedAssets, ...mdxConfigAssets] };
483
+ assets.push(...mdxConfigAssets);
482
484
  }
483
- return { assets: processedAssets };
485
+ return { dependencies, assets };
484
486
  } catch (error) {
485
487
  if (error instanceof BundleError) {
486
488
  console.error("[ecopages] Bundle errors:", error.logs);
@@ -529,13 +531,28 @@ class ReactRenderer extends IntegrationRenderer {
529
531
  } : void 0,
530
532
  htmlTemplate: HtmlTemplate,
531
533
  metadata,
532
- pageProps: allPageProps,
533
- documentProps: this.buildNonReactDocumentProps(HtmlTemplate, allPageProps)
534
+ pageProps: allPageProps
534
535
  });
535
536
  } catch (error) {
536
537
  throw this.createRenderError("Failed to render component", error);
537
538
  }
538
539
  }
540
+ getHtmlDocumentContributions(options) {
541
+ if (options.partial || !options.renderOptions) {
542
+ return void 0;
543
+ }
544
+ const safeLocals = this.pagePayloadService.getSerializableLocals(
545
+ options.renderOptions.locals,
546
+ this.getComponentRequires(options.renderOptions.Page)
547
+ );
548
+ const allPageProps = this.pagePayloadService.buildSerializedPageProps({
549
+ pageProps: options.renderOptions.pageProps,
550
+ params: options.renderOptions.params,
551
+ query: options.renderOptions.query,
552
+ safeLocals
553
+ });
554
+ return this.buildNonReactDocumentContributions(options.renderOptions.HtmlTemplate, allPageProps);
555
+ }
539
556
  getDocumentAttributes() {
540
557
  return this.getRouterDocumentAttributes();
541
558
  }
@@ -581,8 +598,7 @@ class ReactRenderer extends IntegrationRenderer {
581
598
  component: HtmlTemplate,
582
599
  props: {
583
600
  metadata,
584
- pageProps: normalizedProps,
585
- ...this.buildNonReactDocumentProps(HtmlTemplate, normalizedProps) ?? {}
601
+ pageProps: normalizedProps
586
602
  },
587
603
  children: layoutRender?.html ?? viewRender.html
588
604
  });
@@ -590,7 +606,8 @@ class ReactRenderer extends IntegrationRenderer {
590
606
  const transformedHtml = await this.finalizeResolvedHtml({
591
607
  html: `${this.DOC_TYPE}${documentRender.html}`,
592
608
  partial: false,
593
- documentAttributes: this.getRouterDocumentAttributes()
609
+ documentAttributes: this.getRouterDocumentAttributes(),
610
+ htmlContributions: this.buildNonReactDocumentContributions(HtmlTemplate, normalizedProps)
594
611
  });
595
612
  return this.createHtmlResponse(transformedHtml, ctx);
596
613
  } catch (error) {
@@ -63,12 +63,20 @@ export declare class ReactHydrationAssetService {
63
63
  */
64
64
  buildComponentRenderAssets(componentFile: string, config?: EcoComponentConfig): Promise<ProcessedAsset[]>;
65
65
  /**
66
- * Builds the Page Browser Graph assets for a React page.
66
+ * Creates the Page Browser Graph dependency declarations for a React page.
67
67
  *
68
68
  * @param pagePath - Absolute file path of the page
69
69
  * @param isMdx - Whether the page is an MDX file
70
70
  * @param declaredModules - Explicitly declared browser module specifiers
71
- * @returns Processed assets for the route
71
+ * @returns Declarative assets for core-owned processing
72
+ */
73
+ createPageBrowserGraphDependencies(pagePath: string, isMdx: boolean, declaredModules: string[]): Promise<AssetDefinition[]>;
74
+ /**
75
+ * Builds the Page Browser Graph assets for a React page.
76
+ *
77
+ * @remarks
78
+ * Kept as a compatibility wrapper while callers migrate to core-owned page
79
+ * graph assembly.
72
80
  */
73
81
  buildPageBrowserGraphAssets(pagePath: string, isMdx: boolean, declaredModules: string[]): Promise<ProcessedAsset[]>;
74
82
  }
@@ -146,14 +146,14 @@ class ReactHydrationAssetService {
146
146
  return this.config.assetProcessingService.processDependencies(dependencies, componentName);
147
147
  }
148
148
  /**
149
- * Builds the Page Browser Graph assets for a React page.
149
+ * Creates the Page Browser Graph dependency declarations for a React page.
150
150
  *
151
151
  * @param pagePath - Absolute file path of the page
152
152
  * @param isMdx - Whether the page is an MDX file
153
153
  * @param declaredModules - Explicitly declared browser module specifiers
154
- * @returns Processed assets for the route
154
+ * @returns Declarative assets for core-owned processing
155
155
  */
156
- async buildPageBrowserGraphAssets(pagePath, isMdx, declaredModules) {
156
+ async createPageBrowserGraphDependencies(pagePath, isMdx, declaredModules) {
157
157
  const componentName = `ecopages-react-${rapidhash(pagePath)}`;
158
158
  const hmrManager = this.config.assetProcessingService?.getHmrManager();
159
159
  const isDevelopment = hmrManager?.isEnabled() ?? false;
@@ -180,6 +180,18 @@ class ReactHydrationAssetService {
180
180
  useBrowserRuntimeImports,
181
181
  isMdx
182
182
  );
183
+ return dependencies;
184
+ }
185
+ /**
186
+ * Builds the Page Browser Graph assets for a React page.
187
+ *
188
+ * @remarks
189
+ * Kept as a compatibility wrapper while callers migrate to core-owned page
190
+ * graph assembly.
191
+ */
192
+ async buildPageBrowserGraphAssets(pagePath, isMdx, declaredModules) {
193
+ const componentName = `ecopages-react-${rapidhash(pagePath)}`;
194
+ const dependencies = await this.createPageBrowserGraphDependencies(pagePath, isMdx, declaredModules);
183
195
  if (!this.config.assetProcessingService) {
184
196
  throw new Error("AssetProcessingService is not set");
185
197
  }
@@ -38,7 +38,8 @@ class ReactPageModuleService {
38
38
  const fileHash = fileSystem.hash(filePath);
39
39
  const cacheScopeSuffix = options?.cacheScope ? `-${sanitizeCacheScope(options.cacheScope)}` : "";
40
40
  const cacheBuster = options?.bypassCache || process?.env?.NODE_ENV === "development" ? `-${Date.now()}` : "";
41
- const outputFileName = `${fileBaseName}-${fileHash}${cacheScopeSuffix}${cacheBuster}.js`;
41
+ const outputFileName = `${fileBaseName}-${fileHash}${cacheScopeSuffix}${cacheBuster}.mjs`;
42
+ const outputNamingTemplate = `${fileBaseName}-${fileHash}${cacheScopeSuffix}${cacheBuster}.[ext]`;
42
43
  const buildResult = await build(
43
44
  {
44
45
  entrypoints: [filePath],
@@ -50,7 +51,7 @@ class ReactPageModuleService {
50
51
  splitting: false,
51
52
  minify: false,
52
53
  treeshaking: false,
53
- naming: outputFileName,
54
+ naming: outputNamingTemplate,
54
55
  plugins: [mdxPlugin]
55
56
  },
56
57
  this.config.buildExecutor
@@ -60,7 +61,7 @@ class ReactPageModuleService {
60
61
  throw new Error(`Failed to compile MDX page module: ${details}`);
61
62
  }
62
63
  const preferredOutputPath = path.join(outdir, outputFileName);
63
- const compiledOutput = buildResult.outputs.find((output) => output.path === preferredOutputPath)?.path ?? buildResult.outputs.find((output) => output.path.endsWith(".js"))?.path;
64
+ const compiledOutput = buildResult.outputs.find((output) => output.path === preferredOutputPath)?.path ?? buildResult.outputs.find((output) => /\.(?:[cm]?js)$/u.test(output.path))?.path;
64
65
  if (!compiledOutput) {
65
66
  throw new Error(`No compiled MDX output generated for page: ${filePath}`);
66
67
  }