@ecopages/core 0.2.0-beta.37 → 0.2.0-beta.38

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 (35) hide show
  1. package/package.json +11 -3
  2. package/src/eco/eco.browser.js +9 -2
  3. package/src/eco/eco.js +8 -2
  4. package/src/eco/eco.types.d.ts +29 -2
  5. package/src/route-renderer/README.md +2 -1
  6. package/src/route-renderer/orchestration/integration-renderer.d.ts +19 -3
  7. package/src/route-renderer/orchestration/integration-renderer.js +89 -8
  8. package/src/route-renderer/orchestration/page-browser-graph/grouped-graph-build-plan.d.ts +15 -0
  9. package/src/route-renderer/orchestration/page-browser-graph/grouped-graph-build-plan.js +9 -0
  10. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.merge.d.ts +5 -0
  11. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.merge.js +30 -0
  12. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.d.ts +22 -6
  13. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js +145 -68
  14. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.d.ts +9 -2
  15. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.js +56 -39
  16. package/src/route-renderer/orchestration/page-browser-graph/route-instance-key.d.ts +29 -0
  17. package/src/route-renderer/orchestration/page-browser-graph/route-instance-key.js +54 -0
  18. package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.d.ts +3 -0
  19. package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.js +3 -2
  20. package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.builder.d.ts +1 -0
  21. package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.builder.js +3 -5
  22. package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.d.ts +14 -4
  23. package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +31 -4
  24. package/src/route-renderer/page-loading/file-scoped-dependency-components.d.ts +42 -0
  25. package/src/route-renderer/page-loading/file-scoped-dependency-components.js +93 -0
  26. package/src/route-renderer/page-loading/page-module-loader.d.ts +1 -0
  27. package/src/route-renderer/page-loading/page-module-loader.js +1 -0
  28. package/src/route-renderer/page-loading/resolved-page-dependencies.d.ts +12 -0
  29. package/src/route-renderer/page-loading/resolved-page-dependencies.js +38 -0
  30. package/src/static-site-generator/production-page-browser-graph-prebuild.d.ts +10 -6
  31. package/src/static-site-generator/production-page-browser-graph-prebuild.js +29 -10
  32. package/src/static-site-generator/static-site-generator.js +4 -1
  33. package/src/types/public-types.d.ts +9 -1
  34. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.loader.d.ts +0 -9
  35. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.loader.js +0 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-beta.37",
3
+ "version": "0.2.0-beta.38",
4
4
  "description": "Core package for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -17,7 +17,7 @@
17
17
  "directory": "packages/core"
18
18
  },
19
19
  "dependencies": {
20
- "@ecopages/file-system": "0.2.0-beta.37",
20
+ "@ecopages/file-system": "0.2.0-beta.38",
21
21
  "@ecopages/logger": "^0.2.3",
22
22
  "@ecopages/scripts-injector": "^0.1.5",
23
23
  "@oxc-project/runtime": "0.141.0",
@@ -32,7 +32,7 @@
32
32
  "@standard-schema/utils": "^0.3.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@ecopages/dev-toolbar": "0.2.0-beta.37"
35
+ "@ecopages/dev-toolbar": "0.2.0-beta.38"
36
36
  },
37
37
  "peerDependenciesMeta": {
38
38
  "@ecopages/dev-toolbar": {
@@ -80,6 +80,10 @@
80
80
  "types": "./src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.d.ts",
81
81
  "default": "./src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js"
82
82
  },
83
+ "./route-renderer/page-loading/file-scoped-dependency-components": {
84
+ "types": "./src/route-renderer/page-loading/file-scoped-dependency-components.d.ts",
85
+ "default": "./src/route-renderer/page-loading/file-scoped-dependency-components.js"
86
+ },
83
87
  "./route-renderer/orchestration/foreign-child/owning-renderer-resolution": {
84
88
  "types": "./src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.d.ts",
85
89
  "default": "./src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js"
@@ -357,6 +361,10 @@
357
361
  "types": "./src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.d.ts",
358
362
  "default": "./src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js"
359
363
  },
364
+ "./route-renderer/page-loading/file-scoped-dependency-components.ts": {
365
+ "types": "./src/route-renderer/page-loading/file-scoped-dependency-components.d.ts",
366
+ "default": "./src/route-renderer/page-loading/file-scoped-dependency-components.js"
367
+ },
360
368
  "./route-renderer/orchestration/foreign-child/owning-renderer-resolution.ts": {
361
369
  "types": "./src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.d.ts",
362
370
  "default": "./src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js"
@@ -22,12 +22,16 @@ function layout(options) {
22
22
  return createComponentFactory(options);
23
23
  }
24
24
  function page(options) {
25
- const { layout: pageLayout, dependencies, render, staticPaths, staticProps, metadata, cache, requires, middleware, } = options;
25
+ const { layout: pageLayout, dependencies: dependenciesInput, render, staticPaths, staticProps, metadata, cache, requires, middleware, } = options;
26
26
  const layoutEntries = normalizePageLayouts(pageLayout);
27
+ const resolveDependencies = typeof dependenciesInput === 'function' ? dependenciesInput : undefined;
28
+ const staticDependencies = typeof dependenciesInput === 'function'
29
+ ? mergeLayoutDependencies(undefined, layoutEntries)
30
+ : mergeLayoutDependencies(dependenciesInput, layoutEntries);
27
31
  const pageComponent = createComponentFactory({
28
32
  __eco: options.__eco,
29
33
  integration: options.integration,
30
- dependencies: mergeLayoutDependencies(dependencies, layoutEntries),
34
+ dependencies: staticDependencies,
31
35
  render,
32
36
  });
33
37
  if (pageComponent.config) {
@@ -42,6 +46,9 @@ function page(options) {
42
46
  if (metadata) {
43
47
  pageComponent.metadata = metadata;
44
48
  }
49
+ if (resolveDependencies) {
50
+ pageComponent.resolveDependencies = resolveDependencies;
51
+ }
45
52
  if (cache) {
46
53
  pageComponent.cache = cache;
47
54
  }
package/src/eco/eco.js CHANGED
@@ -110,12 +110,16 @@ function layout(options) {
110
110
  * @returns Eco page component.
111
111
  */
112
112
  function page(options) {
113
- const { layout, dependencies, render, staticPaths, staticProps, metadata, cache, requires, middleware } = options;
113
+ const { layout, dependencies: dependenciesInput, render, staticPaths, staticProps, metadata, cache, requires, middleware, } = options;
114
114
  const layoutEntries = normalizePageLayouts(layout);
115
+ const resolveDependencies = typeof dependenciesInput === 'function' ? dependenciesInput : undefined;
116
+ const staticDependencies = typeof dependenciesInput === 'function'
117
+ ? mergeLayoutDependencies(undefined, layoutEntries)
118
+ : mergeLayoutDependencies(dependenciesInput, layoutEntries);
115
119
  const componentOptions = {
116
120
  __eco: options.__eco,
117
121
  integration: options.integration,
118
- dependencies: mergeLayoutDependencies(dependencies, layoutEntries),
122
+ dependencies: staticDependencies,
119
123
  render,
120
124
  };
121
125
  const pageComponent = createComponentFactory(componentOptions);
@@ -128,6 +132,8 @@ function page(options) {
128
132
  pageComponent.staticProps = staticProps;
129
133
  if (metadata)
130
134
  pageComponent.metadata = metadata;
135
+ if (resolveDependencies)
136
+ pageComponent.resolveDependencies = resolveDependencies;
131
137
  if (cache)
132
138
  pageComponent.cache = cache;
133
139
  if (requires)
@@ -2,7 +2,7 @@
2
2
  * Type definitions for the eco namespace API
3
3
  * @module
4
4
  */
5
- import type { DependencyLazyTrigger, EcoComponent, EcoComponentDependencies, EcoHtmlComponent, EcoInjectedMeta, EcoLayoutComponent, EcoPageLayouts, EcoPagesElement, FileRouteMiddleware, GetMetadata, GetStaticPaths, GetStaticProps, HtmlTemplateProps, LayoutProps, RequestLocals, RequestPageContext } from '../types/public-types.js';
5
+ import type { DependencyLazyTrigger, EcoComponent, EcoComponentDependencies, EcoHtmlComponent, EcoInjectedMeta, EcoLayoutComponent, EcoPageLayouts, EcoPagesElement, FileRouteMiddleware, GetMetadata, GetStaticPaths, GetStaticProps, HtmlTemplateProps, LayoutProps, RequestLocals, RequestPageContext, PageParams, PageQuery } from '../types/public-types.js';
6
6
  import type { CacheStrategy } from '../services/cache/cache.types.js';
7
7
  /**
8
8
  * Extracts the props type from one eco component.
@@ -54,6 +54,28 @@ type WithRequiredLocals<K extends keyof RequestLocals> = Omit<RequestLocals, K>
54
54
  };
55
55
  type RequiresKeys = keyof RequestLocals;
56
56
  export type PageRequires<K extends RequiresKeys = RequiresKeys> = K | readonly K[];
57
+ /**
58
+ * Context passed to a page `dependencies` resolver for one concrete route render.
59
+ */
60
+ export type GetPageDependenciesContext<T = Record<string, unknown>> = {
61
+ props: PagePropsFor<T>;
62
+ params?: PageParams;
63
+ query?: PageQuery;
64
+ };
65
+ /**
66
+ * Page dependency bag optionally scoped to a declaring file.
67
+ */
68
+ export type PageDependenciesResult = EcoComponentDependencies & {
69
+ ownerFile?: string;
70
+ };
71
+ /**
72
+ * Resolves request-specific page dependencies for one route render.
73
+ */
74
+ export type GetPageDependencies<T = Record<string, unknown>> = (context: GetPageDependenciesContext<T>) => PageDependenciesResult | undefined | Promise<PageDependenciesResult | undefined>;
75
+ /**
76
+ * Static page dependencies or a per-render resolver.
77
+ */
78
+ export type PageDependenciesInput<T = Record<string, unknown>> = EcoComponentDependencies | GetPageDependencies<T>;
57
79
  /**
58
80
  * Lazy trigger options map directly to scripts-injector attributes.
59
81
  * Only one trigger type can be active at a time.
@@ -91,7 +113,11 @@ export interface PageOptionsBase<T, E = EcoPagesElement> {
91
113
  /** @internal Injected by eco-component-meta-plugin */
92
114
  __eco?: EcoInjectedMeta;
93
115
  integration?: string;
94
- dependencies?: EcoComponentDependencies;
116
+ /**
117
+ * Declares browser dependencies for the page, or resolves them per render after
118
+ * `staticProps` are available.
119
+ */
120
+ dependencies?: PageDependenciesInput<T>;
95
121
  layout?: EcoPageLayouts<E>;
96
122
  /**
97
123
  * Define static paths for dynamic routes (e.g., [slug].tsx).
@@ -178,6 +204,7 @@ export type EcoPageComponent<T> = EcoComponent<PagePropsFor<T> & Partial<Request
178
204
  staticPaths?: GetStaticPaths;
179
205
  staticProps?: GetStaticProps<T>;
180
206
  metadata?: GetMetadata<T>;
207
+ resolveDependencies?: GetPageDependencies<T>;
181
208
  cache?: CacheStrategy;
182
209
  requires?: PageRequires;
183
210
  middleware?: FileRouteMiddleware[];
@@ -84,7 +84,7 @@ The route-render contract is:
84
84
 
85
85
  1. `RouteRendererFactory` selects the owning integration renderer.
86
86
  2. `IntegrationRenderer.execute()` delegates preparation and finalization to `RouteRenderOrchestrator`.
87
- 3. `RouteRenderOrchestrator.prepareRenderOptions()` loads the page module, validates ownership (fail-fast), resolves page data, resolves dependencies, and builds the page browser graph.
87
+ 3. `RouteRenderOrchestrator.prepareRenderOptions()` loads the page module, validates ownership (fail-fast), resolves final page props, resolves dependencies, and builds the page browser graph. Graph cache identity includes route params and query; contribution collection receives those same final props.
88
88
  4. The integration renderer performs page, layout, and document-shell rendering. When it encounters a foreign child, it delegates that child back to the owning renderer.
89
89
  5. If a renderer needs queued handoff, it emits internal foreign-subtree tokens and resolves them before returning final HTML.
90
90
  6. `RouteRenderOrchestrator.executePrepared()` captures the final body, rejects unresolved `<eco-marker>` artifacts, stamps document attributes when needed, and runs the HTML transformer.
@@ -93,6 +93,7 @@ Important:
93
93
 
94
94
  - route-level fallback resolution is gone; unresolved artifacts are now a hard failure
95
95
  - ownership is declared from component metadata, not inferred from final HTML
96
+ - declared page dependencies are resolved from final render inputs and carried to the owning integration; integrations may use them for renderer-specific lifecycle work such as HMR ownership
96
97
  - same-integration children stay renderer-local and do not need to pass through a universal transport
97
98
 
98
99
  ## Declared Foreign Child Contract
@@ -4,7 +4,7 @@
4
4
  * @module
5
5
  */
6
6
  import type { EcoPagesAppConfig, IHmrManager } from '../../types/internal-types.js';
7
- import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPageFile, EcoPagesElement, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
7
+ import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoComponentDependencies, EcoPageFile, EcoPagesElement, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
8
8
  import { type AssetProcessingService, type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
9
9
  import { HtmlTransformerService } from '../../services/html/html-transformer.service.js';
10
10
  import type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js';
@@ -12,6 +12,8 @@ import { HttpError } from '../../errors/http-error.js';
12
12
  import { DependencyResolverService } from '../page-loading/dependency-resolver.js';
13
13
  import { PageModuleLoaderService } from '../page-loading/page-module-loader.js';
14
14
  import { RouteRenderOrchestrator, type RouteRenderOrchestratorAdapter, type RouteRenderOrchestratorResolvedInputs } from './route-pipeline/route-render-orchestrator.js';
15
+ import { type ResolvedPageDependencies } from '../page-loading/resolved-page-dependencies.js';
16
+ import { type GroupedGraphBuildPlan } from './page-browser-graph/grouped-graph-build-plan.js';
15
17
  import type { ForeignChildRuntime } from './foreign-child/component-render-context.js';
16
18
  import { ForeignSubtreeExecutionService } from './foreign-child/foreign-subtree-execution.service.js';
17
19
  /**
@@ -72,9 +74,19 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
72
74
  */
73
75
  protected ensureIntegrationRuntimeActivated(): Promise<void>;
74
76
  /**
75
- * Prebuilds the production Page Browser Graph for one route file.
77
+ * Prebuilds the production Page Browser Graph for one route file and optional params.
76
78
  */
77
- prebuildProductionPageBrowserGraph(routeFile: string): Promise<void>;
79
+ prebuildProductionPageBrowserGraph(routeFile: string, options?: Pick<RouteRendererOptions, 'params' | 'query'> & {
80
+ groupedBuildPlan?: GroupedGraphBuildPlan;
81
+ }): Promise<void>;
82
+ /**
83
+ * Builds one production grouped graph plan for the supplied static route instances.
84
+ */
85
+ buildGroupedGraphBuildPlan(instances: ReadonlyArray<{
86
+ routeFile: string;
87
+ params?: RouteRendererOptions['params'];
88
+ query?: RouteRendererOptions['query'];
89
+ }>): Promise<GroupedGraphBuildPlan>;
78
90
  /**
79
91
  * Loads one route module through the owning renderer's import path.
80
92
  *
@@ -118,6 +130,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
118
130
  */
119
131
  protected prepareViewDependencies(view: EcoComponent, layout?: EcoComponent): Promise<ProcessedAsset[]>;
120
132
  protected resolvePageBrowserGraphForFile(filePath: string): Promise<PageBrowserGraphResult | undefined>;
133
+ protected resolvePageBrowserGraphForRoute(filePath: string, routeOptions?: Pick<RouteRendererOptions, 'params' | 'query'>, groupedBuildPlan?: GroupedGraphBuildPlan): Promise<PageBrowserGraphResult | undefined>;
121
134
  /**
122
135
  * Merges component-scoped assets into the active HTML transformer state.
123
136
  *
@@ -270,6 +283,9 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
270
283
  */
271
284
  protected createRouteRenderOrchestratorAdapter(): RouteRenderOrchestratorAdapter<C>;
272
285
  protected resolveRouteRenderInputs(routeOptions: RouteRendererOptions): Promise<RouteRenderOrchestratorResolvedInputs>;
286
+ protected buildPageBrowserGraphContributionContext(routeFile: string, routeOptions?: Pick<RouteRendererOptions, 'params' | 'query'>): Promise<PageBrowserGraphContributionContext>;
287
+ protected resolvePageDependencies(context: PageBrowserGraphContributionContext): Promise<ResolvedPageDependencies | undefined>;
288
+ protected resolvePageBrowserGraphContributionFromDependencies(dependencies: EcoComponentDependencies, ownerFile: string): Promise<PageBrowserGraphContribution | undefined>;
273
289
  protected resolveRouteDependencies(input: {
274
290
  components: (EcoComponent | Partial<EcoComponent>)[];
275
291
  }): Promise<{
@@ -13,7 +13,11 @@ import { OwnershipValidationService } from './ownership-graph/ownership-validati
13
13
  import { hasForeignChildDescendantsInGraph } from './ownership-graph/component-graph-collectors.js';
14
14
  import { RouteRenderOrchestrator, } from './route-pipeline/route-render-orchestrator.js';
15
15
  import { createIntegrationRouteRenderAdapter } from './route-pipeline/integration-route-render-adapter.js';
16
- import { loadPageBrowserGraphContribution } from './page-browser-graph/page-browser-graph-contribution.loader.js';
16
+ import { createPageDependencyInstanceKey } from './page-browser-graph/route-instance-key.js';
17
+ import { mergePageBrowserGraphContributions } from './page-browser-graph/page-browser-graph-contribution.merge.js';
18
+ import { collectDependencyWatchPaths, collectFileScopedDependencyComponents, } from '../page-loading/file-scoped-dependency-components.js';
19
+ import { resolvePageDependenciesFromContext, } from '../page-loading/resolved-page-dependencies.js';
20
+ import { createGroupedGraphBuildPlanKey, } from './page-browser-graph/grouped-graph-build-plan.js';
17
21
  import { normalizeUnresolvedMarkerArtifactHtml } from './route-pipeline/marker-artifact.utils.js';
18
22
  import { isMarkupNodeLike } from './foreign-child/foreign-child-output.utils.js';
19
23
  import { getForeignSubtreeResolutionContextKey, getForeignSubtreeTokenPrefix, resolveOwningIntegrationRenderer, } from './foreign-child/owning-renderer-resolution.js';
@@ -66,11 +70,30 @@ export class IntegrationRenderer {
66
70
  });
67
71
  }
68
72
  /**
69
- * Prebuilds the production Page Browser Graph for one route file.
73
+ * Prebuilds the production Page Browser Graph for one route file and optional params.
70
74
  */
71
- async prebuildProductionPageBrowserGraph(routeFile) {
75
+ async prebuildProductionPageBrowserGraph(routeFile, options) {
72
76
  await this.ensureIntegrationRuntimeActivated();
73
- await this.resolvePageBrowserGraphForFile(routeFile);
77
+ await this.resolvePageBrowserGraphForRoute(routeFile, options, options?.groupedBuildPlan);
78
+ }
79
+ /**
80
+ * Builds one production grouped graph plan for the supplied static route instances.
81
+ */
82
+ async buildGroupedGraphBuildPlan(instances) {
83
+ const builtInstances = await Promise.all(instances.map(async (routeInstance) => {
84
+ const context = await this.buildPageBrowserGraphContributionContext(routeInstance.routeFile, routeInstance);
85
+ const resolvedPageDependencies = await this.resolvePageDependencies(context);
86
+ return {
87
+ routeFile: routeInstance.routeFile,
88
+ dependencyInstanceKey: context.dependencyInstanceKey ?? '',
89
+ contribution: mergePageBrowserGraphContributions(await this.collectPageBrowserGraphContribution(context), resolvedPageDependencies?.contribution),
90
+ };
91
+ }));
92
+ return {
93
+ integrationName: this.name,
94
+ planKey: createGroupedGraphBuildPlanKey(this.name, builtInstances),
95
+ instances: builtInstances,
96
+ };
74
97
  }
75
98
  /**
76
99
  * Loads one route module through the owning renderer's import path.
@@ -173,10 +196,23 @@ export class IntegrationRenderer {
173
196
  return resolvedDependencies;
174
197
  }
175
198
  async resolvePageBrowserGraphForFile(filePath) {
199
+ /**
200
+ * @remarks
201
+ * Integration renderers such as React still call this file-only entry point
202
+ * during explicit view rendering. Route-instance-aware callers should prefer
203
+ * `resolvePageBrowserGraphForRoute`.
204
+ */
205
+ return this.resolvePageBrowserGraphForRoute(filePath);
206
+ }
207
+ async resolvePageBrowserGraphForRoute(filePath, routeOptions, groupedBuildPlan) {
208
+ const graphContext = await this.buildPageBrowserGraphContributionContext(filePath, routeOptions);
209
+ const resolvedPageDependencies = await this.resolvePageDependencies(graphContext);
176
210
  return await this.routeRenderOrchestrator.resolveDeclaredPageBrowserGraph({
177
211
  routeFile: filePath,
212
+ dependencyInstanceKey: graphContext.dependencyInstanceKey,
178
213
  integrationName: this.name,
179
- collectContribution: (routeFile) => loadPageBrowserGraphContribution(routeFile, (targetFile) => this.importPageFile(targetFile), (context) => this.collectPageBrowserGraphContribution(context)),
214
+ groupedBuildPlan,
215
+ collectContribution: async () => mergePageBrowserGraphContributions(await this.collectPageBrowserGraphContribution(graphContext), resolvedPageDependencies?.contribution),
180
216
  });
181
217
  }
182
218
  /**
@@ -494,6 +530,8 @@ export class IntegrationRenderer {
494
530
  resolveRouteDependencies: (input) => this.resolveRouteDependencies(input),
495
531
  importPageFile: (file) => this.importPageFile(file),
496
532
  collectPageBrowserGraphContribution: (context) => this.collectPageBrowserGraphContribution(context),
533
+ resolvePageDependencies: (context) => this.resolvePageDependencies(context),
534
+ buildPageBrowserGraphContributionContext: (routeFile, routeOptions) => this.buildPageBrowserGraphContributionContext(routeFile, routeOptions),
497
535
  renderRouteBody: (renderOptions) => this.renderRouteBody(renderOptions),
498
536
  getDocumentAttributes: (renderOptions) => this.getDocumentAttributes(renderOptions),
499
537
  getHtmlDocumentContributions: (options) => this.getHtmlDocumentContributions(options),
@@ -506,20 +544,21 @@ export class IntegrationRenderer {
506
544
  });
507
545
  }
508
546
  async resolveRouteRenderInputs(routeOptions) {
509
- const pageModule = await this.pageModuleLoaderService.resolvePageModule({
547
+ const resolvedPageModule = await this.pageModuleLoaderService.resolvePageModule({
510
548
  file: routeOptions.file,
511
549
  importPageFileFn: (targetFile) => this.importPageFile(targetFile),
512
550
  });
513
- const { Page, integrationSpecificProps } = pageModule;
551
+ const { Page, integrationSpecificProps, module: pageModule } = resolvedPageModule;
514
552
  const HtmlTemplate = await this.getHtmlTemplate();
515
553
  const Layouts = resolvePageLayoutComponents(Page.config?.layouts);
516
554
  const Layout = resolveInnermostPageLayout(Layouts);
517
555
  const { props, metadata } = await this.pageModuleLoaderService.resolvePageData({
518
- pageModule,
556
+ pageModule: resolvedPageModule,
519
557
  routeOptions,
520
558
  });
521
559
  return {
522
560
  Page,
561
+ pageModule,
523
562
  HtmlTemplate: HtmlTemplate,
524
563
  Layouts,
525
564
  Layout,
@@ -529,6 +568,48 @@ export class IntegrationRenderer {
529
568
  integrationSpecificProps,
530
569
  };
531
570
  }
571
+ async buildPageBrowserGraphContributionContext(routeFile, routeOptions) {
572
+ const { module: pageModule, ...resolvedPageModule } = await this.pageModuleLoaderService.resolvePageModule({
573
+ file: routeFile,
574
+ importPageFileFn: (targetFile) => this.importPageFile(targetFile),
575
+ });
576
+ const { props } = await this.pageModuleLoaderService.resolvePageData({
577
+ pageModule: resolvedPageModule,
578
+ routeOptions: {
579
+ file: routeFile,
580
+ params: routeOptions?.params,
581
+ query: routeOptions?.query,
582
+ },
583
+ });
584
+ return {
585
+ file: routeFile,
586
+ pageModule,
587
+ props,
588
+ params: routeOptions?.params,
589
+ query: routeOptions?.query,
590
+ dependencyInstanceKey: createPageDependencyInstanceKey({
591
+ params: routeOptions?.params,
592
+ query: routeOptions?.query,
593
+ }),
594
+ };
595
+ }
596
+ async resolvePageDependencies(context) {
597
+ return resolvePageDependenciesFromContext(context, this.name, (dependencies, ownerFile) => this.resolvePageBrowserGraphContributionFromDependencies(dependencies, ownerFile));
598
+ }
599
+ async resolvePageBrowserGraphContributionFromDependencies(dependencies, ownerFile) {
600
+ const components = collectFileScopedDependencyComponents({
601
+ ownerFile,
602
+ integrationName: this.name,
603
+ dependencies,
604
+ });
605
+ if (components.length === 0) {
606
+ return undefined;
607
+ }
608
+ return {
609
+ assets: await this.processComponentDependencies(components),
610
+ watchPaths: collectDependencyWatchPaths(ownerFile, components),
611
+ };
612
+ }
532
613
  async resolveRouteDependencies(input) {
533
614
  return {
534
615
  resolvedDependencies: await this.resolveDependencies(input.components),
@@ -0,0 +1,15 @@
1
+ import type { PageBrowserGraphContribution } from '../../../types/public-types.js';
2
+ export type GroupedGraphBuildPlanInstance = {
3
+ routeFile: string;
4
+ dependencyInstanceKey: string;
5
+ contribution?: PageBrowserGraphContribution;
6
+ };
7
+ export type GroupedGraphBuildPlan = {
8
+ integrationName: string;
9
+ planKey: string;
10
+ instances: ReadonlyArray<GroupedGraphBuildPlanInstance>;
11
+ };
12
+ /**
13
+ * Builds a stable grouped-cache identity for one integration static export plan.
14
+ */
15
+ export declare function createGroupedGraphBuildPlanKey(integrationName: string, instances: ReadonlyArray<Pick<GroupedGraphBuildPlanInstance, 'routeFile' | 'dependencyInstanceKey'>>): string;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Builds a stable grouped-cache identity for one integration static export plan.
3
+ */
4
+ export function createGroupedGraphBuildPlanKey(integrationName, instances) {
5
+ const canonicalInstances = [...instances]
6
+ .map((instance) => [instance.routeFile, instance.dependencyInstanceKey])
7
+ .sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
8
+ return JSON.stringify(['grouped-plan', integrationName, canonicalInstances]);
9
+ }
@@ -0,0 +1,5 @@
1
+ import type { PageBrowserGraphContribution } from '../../../types/public-types.js';
2
+ /**
3
+ * Merges multiple Page Browser Graph contributions into one declarative payload.
4
+ */
5
+ export declare function mergePageBrowserGraphContributions(...contributions: Array<PageBrowserGraphContribution | undefined>): PageBrowserGraphContribution | undefined;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Merges multiple Page Browser Graph contributions into one declarative payload.
3
+ */
4
+ export function mergePageBrowserGraphContributions(...contributions) {
5
+ const dependencies = [];
6
+ const assets = [];
7
+ const watchPaths = new Set();
8
+ for (const contribution of contributions) {
9
+ if (!contribution) {
10
+ continue;
11
+ }
12
+ if (contribution.dependencies?.length) {
13
+ dependencies.push(...contribution.dependencies);
14
+ }
15
+ if (contribution.assets?.length) {
16
+ assets.push(...contribution.assets);
17
+ }
18
+ for (const watchPath of contribution.watchPaths ?? []) {
19
+ watchPaths.add(watchPath);
20
+ }
21
+ }
22
+ if (dependencies.length === 0 && assets.length === 0 && watchPaths.size === 0) {
23
+ return undefined;
24
+ }
25
+ return {
26
+ ...(dependencies.length > 0 ? { dependencies } : {}),
27
+ ...(assets.length > 0 ? { assets } : {}),
28
+ ...(watchPaths.size > 0 ? { watchPaths: Array.from(watchPaths) } : {}),
29
+ };
30
+ }
@@ -1,3 +1,4 @@
1
+ import type { GroupedGraphScope } from './route-instance-key.js';
1
2
  import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
3
  import type { PageBrowserGraphContribution, PageBrowserGraphResult } from '../../../types/public-types.js';
3
4
  import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/assets.types.js';
@@ -5,6 +6,7 @@ export type GraphPolicy = 'development' | 'production';
5
6
  export type GraphKey = {
6
7
  integrationName: string;
7
8
  routeFile: string;
9
+ dependencyInstanceKey: string;
8
10
  entryFingerprint: string;
9
11
  policy: GraphPolicy;
10
12
  };
@@ -18,6 +20,11 @@ export type GraphRecord = {
18
20
  export type GraphBuildResult = {
19
21
  result: PageBrowserGraphResult;
20
22
  dependencyPaths: ReadonlySet<string>;
23
+ /**
24
+ * @remarks
25
+ * When false, the build result is returned to callers but not committed to the session cache.
26
+ */
27
+ cacheable?: boolean;
21
28
  };
22
29
  type GroupedGraphRecord = {
23
30
  assetsByRoute: Map<string, ProcessedAsset[]>;
@@ -32,6 +39,7 @@ export type GroupedGraphBuildOutcome = GroupedGraphRecord | {
32
39
  export type AffectedGraphIdentity = {
33
40
  integrationName: string;
34
41
  routeFile: string;
42
+ dependencyInstanceKey: string;
35
43
  policy: GraphPolicy;
36
44
  entryFingerprint: string;
37
45
  };
@@ -42,8 +50,10 @@ export declare class GraphDependencyIndex {
42
50
  private readonly dependencyToGraphKeys;
43
51
  bindRecord(record: GraphRecord): void;
44
52
  unbindRecord(record: GraphRecord): void;
45
- bindGroupedRecord(integrationName: string, record: GroupedGraphRecord): void;
46
- unbindGroupedRecord(integrationName: string, record: GroupedGraphRecord): void;
53
+ bindGroupedRecord(cacheKey: string, record: GroupedGraphRecord): void;
54
+ unbindGroupedRecord(cacheKey: string, record: GroupedGraphRecord): void;
55
+ bindPaths(serializedKey: string, dependencyPaths: ReadonlySet<string>): void;
56
+ unbindPaths(serializedKey: string, dependencyPaths: ReadonlySet<string>): void;
47
57
  getAffectedKeys(filePath: string): Set<string>;
48
58
  resetForTests(): void;
49
59
  private addDependencyBinding;
@@ -55,21 +65,23 @@ export declare class GraphDependencyIndex {
55
65
  export declare class SessionPageBrowserGraphCache {
56
66
  private readonly records;
57
67
  private readonly inFlight;
68
+ private readonly inFlightDependencyPaths;
69
+ private readonly inFlightGenerations;
58
70
  private readonly groupedRecords;
59
71
  private readonly groupedInFlight;
60
72
  private readonly dependencyIndex;
61
73
  private readonly graphGenerations;
62
74
  private readonly groupedGenerations;
63
75
  private buildCount;
64
- getGraphByRoute(integrationName: string, routeFile: string, policy: GraphPolicy): PageBrowserGraphResult | undefined;
65
76
  /**
66
77
  * Page Browser Graph compilations in this dev-server session.
67
78
  */
68
79
  getBuildCount(): number;
80
+ getGraphByRoute(integrationName: string, routeFile: string, policy: GraphPolicy, dependencyInstanceKey?: string): PageBrowserGraphResult | undefined;
69
81
  getAffectedGraphIdentities(filePath: string): AffectedGraphIdentity[];
70
- resolveGraph(key: GraphKey, build: () => Promise<GraphBuildResult | undefined>): Promise<PageBrowserGraphResult | undefined>;
71
- resolveGroupedGraph(integrationName: string, build: () => Promise<GroupedGraphBuildOutcome | undefined>): Promise<Map<string, ProcessedAsset[]>>;
72
- peekGroupedGraph(integrationName: string): Map<string, ProcessedAsset[]> | undefined;
82
+ resolveGraph(key: GraphKey, provisionalDependencyPaths: ReadonlySet<string>, build: () => Promise<GraphBuildResult | undefined>): Promise<PageBrowserGraphResult | undefined>;
83
+ resolveGroupedGraph(scope: GroupedGraphScope, build: () => Promise<GroupedGraphBuildOutcome | undefined>): Promise<Map<string, ProcessedAsset[]>>;
84
+ peekGroupedGraph(scope: GroupedGraphScope): Map<string, ProcessedAsset[]> | undefined;
73
85
  invalidateByFilePath(filePath: string): number;
74
86
  invalidateRoute(integrationName: string, routeFile: string): void;
75
87
  invalidateByRouteFile(routeFile: string): void;
@@ -85,6 +97,10 @@ export declare class SessionPageBrowserGraphCache {
85
97
  private runGroupedBuild;
86
98
  private canCommitGraphBuild;
87
99
  private canCommitGroupedBuild;
100
+ private bindDependencyPaths;
101
+ private unbindDependencyPaths;
102
+ private clearInFlightBuild;
103
+ private releaseInFlightDependencyPaths;
88
104
  }
89
105
  /**
90
106
  * Returns the session graph cache for one app instance.