@ecopages/core 0.2.0-beta.27 → 0.2.0-beta.29

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 (103) hide show
  1. package/README.md +1 -1
  2. package/package.json +42 -26
  3. package/src/adapters/abstract/application-adapter.d.ts +22 -4
  4. package/src/adapters/abstract/application-adapter.js +27 -4
  5. package/src/adapters/abstract/server-adapter.d.ts +10 -0
  6. package/src/adapters/bun/create-app.d.ts +1 -0
  7. package/src/adapters/bun/create-app.js +11 -4
  8. package/src/adapters/bun/server-adapter.js +1 -0
  9. package/src/adapters/node/create-app.d.ts +1 -0
  10. package/src/adapters/node/create-app.js +10 -3
  11. package/src/adapters/node/server-adapter.js +1 -0
  12. package/src/adapters/shared/fs-server-response-factory.d.ts +5 -0
  13. package/src/adapters/shared/fs-server-response-factory.js +20 -0
  14. package/src/adapters/shared/fs-server-response-matcher.d.ts +18 -0
  15. package/src/adapters/shared/fs-server-response-matcher.js +75 -15
  16. package/src/config/config-builder.d.ts +12 -2
  17. package/src/config/config-builder.js +41 -2
  18. package/src/config/constants.d.ts +1 -0
  19. package/src/config/constants.js +2 -1
  20. package/src/eco/eco.js +2 -2
  21. package/src/eco/eco.utils.d.ts +1 -1
  22. package/src/eco/eco.utils.js +1 -1
  23. package/src/hmr/client/hmr-runtime.js +1 -1
  24. package/src/hmr/hmr-file-change-prep.d.ts +1 -1
  25. package/src/hmr/hmr-file-change-prep.js +1 -1
  26. package/src/plugins/processor.d.ts +8 -0
  27. package/src/route-renderer/GRAPH.md +11 -11
  28. package/src/route-renderer/README.md +11 -6
  29. package/src/route-renderer/orchestration/{document-shell-render.service.d.ts → document-shell/document-shell-render.service.d.ts} +19 -2
  30. package/src/route-renderer/orchestration/{document-shell-render.service.js → document-shell/document-shell-render.service.js} +28 -0
  31. package/src/route-renderer/orchestration/{layout-shell-props.service.d.ts → document-shell/layout-shell-props.service.d.ts} +1 -1
  32. package/src/route-renderer/orchestration/{component-render-context.d.ts → foreign-child/component-render-context.d.ts} +1 -1
  33. package/src/route-renderer/orchestration/{component-render-context.js → foreign-child/component-render-context.js} +1 -1
  34. package/src/route-renderer/orchestration/{render-output.utils.d.ts → foreign-child/foreign-child-output.utils.d.ts} +1 -7
  35. package/src/route-renderer/orchestration/{render-output.utils.js → foreign-child/foreign-child-output.utils.js} +1 -26
  36. package/src/route-renderer/orchestration/{foreign-subtree-execution.service.d.ts → foreign-child/foreign-subtree-execution.service.d.ts} +5 -5
  37. package/src/route-renderer/orchestration/{foreign-subtree-execution.service.js → foreign-child/foreign-subtree-execution.service.js} +2 -2
  38. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.d.ts +19 -0
  39. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js +35 -0
  40. package/src/route-renderer/orchestration/integration-renderer.d.ts +4 -46
  41. package/src/route-renderer/orchestration/integration-renderer.js +53 -157
  42. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.d.ts +75 -0
  43. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.js +177 -0
  44. package/src/route-renderer/orchestration/{component-graph-collectors.d.ts → ownership-graph/component-graph-collectors.d.ts} +4 -4
  45. package/src/route-renderer/orchestration/{component-graph-collectors.js → ownership-graph/component-graph-collectors.js} +1 -1
  46. package/src/route-renderer/orchestration/{component-graph.d.ts → ownership-graph/component-graph.d.ts} +1 -3
  47. package/src/route-renderer/orchestration/{component-graph.js → ownership-graph/component-graph.js} +0 -2
  48. package/src/route-renderer/orchestration/{ownership-validation.service.d.ts → ownership-graph/ownership-validation.service.d.ts} +2 -2
  49. package/src/route-renderer/orchestration/{ownership-validation.service.js → ownership-graph/ownership-validation.service.js} +3 -4
  50. package/src/route-renderer/orchestration/{global-injector-assets.service.d.ts → page-browser-graph/global-injector-assets.service.d.ts} +3 -3
  51. package/src/route-renderer/orchestration/{global-injector-assets.service.js → page-browser-graph/global-injector-assets.service.js} +2 -2
  52. package/src/route-renderer/orchestration/{page-browser-graph-contribution.loader.d.ts → page-browser-graph/page-browser-graph-contribution.loader.d.ts} +1 -1
  53. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.d.ts +12 -0
  54. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js +29 -0
  55. package/src/route-renderer/orchestration/{page-browser-graph-session.d.ts → page-browser-graph/page-browser-graph-session.d.ts} +3 -3
  56. package/src/route-renderer/orchestration/{page-browser-graph-session.js → page-browser-graph/page-browser-graph-session.js} +1 -1
  57. package/src/route-renderer/orchestration/{page-browser-graph.service.d.ts → page-browser-graph/page-browser-graph.service.d.ts} +3 -3
  58. package/src/route-renderer/orchestration/{page-browser-graph.service.js → page-browser-graph/page-browser-graph.service.js} +3 -3
  59. package/src/route-renderer/orchestration/{processed-asset-dedupe.d.ts → page-browser-graph/processed-asset-dedupe.d.ts} +1 -1
  60. package/src/route-renderer/orchestration/{integration-route-render-adapter.d.ts → route-pipeline/integration-route-render-adapter.d.ts} +3 -3
  61. package/src/route-renderer/orchestration/{integration-route-render-adapter.js → route-pipeline/integration-route-render-adapter.js} +1 -1
  62. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.d.ts +6 -0
  63. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.js +27 -0
  64. package/src/route-renderer/orchestration/route-pipeline/robots-meta.contribution.d.ts +15 -0
  65. package/src/route-renderer/orchestration/route-pipeline/robots-meta.contribution.js +36 -0
  66. package/src/route-renderer/orchestration/{route-html-finalization.service.d.ts → route-pipeline/route-html-finalization.service.d.ts} +6 -2
  67. package/src/route-renderer/orchestration/{route-html-finalization.service.js → route-pipeline/route-html-finalization.service.js} +5 -2
  68. package/src/route-renderer/orchestration/{route-prepared-options.builder.d.ts → route-pipeline/route-prepared-options.builder.d.ts} +3 -3
  69. package/src/route-renderer/orchestration/{route-prepared-options.builder.js → route-pipeline/route-prepared-options.builder.js} +8 -4
  70. package/src/route-renderer/orchestration/{route-prepared-options.utils.js → route-pipeline/route-prepared-options.utils.js} +1 -1
  71. package/src/route-renderer/orchestration/{route-render-orchestrator.d.ts → route-pipeline/route-render-orchestrator.d.ts} +6 -6
  72. package/src/route-renderer/orchestration/{route-render-orchestrator.js → route-pipeline/route-render-orchestrator.js} +8 -5
  73. package/src/route-renderer/orchestration/string-markup-renderer.js +1 -1
  74. package/src/services/html/html-transformer.service.js +1 -1
  75. package/src/services/invalidation/development-invalidation.service.d.ts +5 -0
  76. package/src/services/invalidation/development-invalidation.service.js +8 -9
  77. package/src/static-site-generator/README.md +9 -1
  78. package/src/static-site-generator/production-page-browser-graph-prebuild.js +1 -1
  79. package/src/static-site-generator/sitemap-routes.d.ts +14 -0
  80. package/src/static-site-generator/sitemap-routes.js +31 -0
  81. package/src/static-site-generator/sitemap.d.ts +15 -0
  82. package/src/static-site-generator/sitemap.js +33 -0
  83. package/src/static-site-generator/static-export-context.d.ts +9 -1
  84. package/src/static-site-generator/static-site-generator.d.ts +27 -6
  85. package/src/static-site-generator/static-site-generator.js +200 -50
  86. package/src/types/internal-types.d.ts +9 -2
  87. package/src/types/public-types.d.ts +78 -2
  88. package/src/utils/ecopages-route-info.d.ts +26 -0
  89. package/src/utils/ecopages-route-info.js +26 -0
  90. package/src/utils/html-escaping.d.ts +7 -0
  91. package/src/utils/html-escaping.js +5 -1
  92. package/src/utils/path-pattern.d.ts +23 -0
  93. package/src/utils/path-pattern.js +27 -0
  94. package/src/watchers/project-watcher.d.ts +3 -3
  95. package/src/watchers/project-watcher.js +4 -4
  96. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +0 -1
  97. package/src/route-renderer/orchestration/declared-ownership-graph.js +0 -8
  98. package/src/route-renderer/orchestration/template-serialization.d.ts +0 -38
  99. package/src/route-renderer/orchestration/template-serialization.js +0 -45
  100. /package/src/route-renderer/orchestration/{layout-shell-props.service.js → document-shell/layout-shell-props.service.js} +0 -0
  101. /package/src/route-renderer/orchestration/{page-browser-graph-contribution.loader.js → page-browser-graph/page-browser-graph-contribution.loader.js} +0 -0
  102. /package/src/route-renderer/orchestration/{processed-asset-dedupe.js → page-browser-graph/processed-asset-dedupe.js} +0 -0
  103. /package/src/route-renderer/orchestration/{route-prepared-options.utils.d.ts → route-pipeline/route-prepared-options.utils.d.ts} +0 -0
@@ -53,6 +53,12 @@ class ConfigBuilder {
53
53
  "*": []
54
54
  }
55
55
  },
56
+ sitemap: {
57
+ enabled: false,
58
+ fileName: "sitemap.xml",
59
+ extraUrls: [],
60
+ exclude: []
61
+ },
56
62
  integrations: [],
57
63
  integrationsDependencies: [],
58
64
  distDir: DEFAULT_ECOPAGES_DIST_DIR,
@@ -74,7 +80,8 @@ class ConfigBuilder {
74
80
  publicDir: "",
75
81
  srcDir: "",
76
82
  htmlTemplatePath: "",
77
- error404TemplatePath: ""
83
+ error404TemplatePath: "",
84
+ error500TemplatePath: ""
78
85
  },
79
86
  processors: /* @__PURE__ */ new Map(),
80
87
  loaders: /* @__PURE__ */ new Map(),
@@ -201,6 +208,31 @@ class ConfigBuilder {
201
208
  this.config.robotsTxt = robotsTxt;
202
209
  return this;
203
210
  }
211
+ /**
212
+ * Sets automatic sitemap.xml generation during static export.
213
+ *
214
+ * @remarks
215
+ * Merged over `{ enabled: false, fileName: 'sitemap.xml', extraUrls: [], exclude: [] }`.
216
+ * Existing apps stay unchanged until `enabled: true` is set. Page-level
217
+ * `metadata.robots.index: false` and metadata resolution failures are handled
218
+ * during static generation (not by this config merge).
219
+ */
220
+ setSitemap(sitemap) {
221
+ const defaults = {
222
+ enabled: false,
223
+ fileName: "sitemap.xml",
224
+ extraUrls: [],
225
+ exclude: []
226
+ };
227
+ this.config.sitemap = {
228
+ ...defaults,
229
+ ...this.config.sitemap,
230
+ ...sitemap,
231
+ extraUrls: sitemap.extraUrls ?? this.config.sitemap.extraUrls ?? defaults.extraUrls,
232
+ exclude: sitemap.exclude ?? this.config.sitemap.exclude ?? defaults.exclude
233
+ };
234
+ return this;
235
+ }
204
236
  /**
205
237
  * Sets the integration plugins to use.
206
238
  * These plugins provide additional functionality to the application.
@@ -381,11 +413,18 @@ class ConfigBuilder {
381
413
  error404TemplatePath: this.resolveSemanticTemplatePath({
382
414
  dirPath: absolutePagesDir,
383
415
  basename: "404"
416
+ }),
417
+ error500TemplatePath: this.resolveSemanticTemplatePath({
418
+ dirPath: absolutePagesDir,
419
+ basename: "500"
384
420
  })
385
421
  };
386
422
  return this;
387
423
  }
388
- resolveSemanticTemplatePath({ dirPath, basename }) {
424
+ resolveSemanticTemplatePath({
425
+ dirPath,
426
+ basename
427
+ }) {
389
428
  const extensions = this.config.templatesExt.length > 0 ? this.config.templatesExt : [".ghtml.ts"];
390
429
  const matches = extensions.map((extension) => path.join(dirPath, `${basename}${extension}`)).filter((candidate) => fileSystem.exists(candidate));
391
430
  invariant(matches.length <= 1, CONFIG_BUILDER_ERRORS.duplicateSemanticTemplate(basename, matches));
@@ -7,6 +7,7 @@
7
7
  */
8
8
  export declare const STATUS_MESSAGE: {
9
9
  404: string;
10
+ 500: string;
10
11
  };
11
12
  /**
12
13
  *Variables used to determine if the app is running on Bun
@@ -1,5 +1,6 @@
1
1
  const STATUS_MESSAGE = {
2
- 404: "404 Not Found"
2
+ 404: "404 Not Found",
3
+ 500: "Internal Server Error"
3
4
  };
4
5
  const IS_BUN = typeof Bun !== "undefined";
5
6
  const RESOLVED_ASSETS_DIR = "assets";
package/src/eco/eco.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  finalizeComponentRender,
3
3
  getComponentRenderContext,
4
4
  interceptForeignChild
5
- } from "../route-renderer/orchestration/component-render-context.js";
6
- import { isThenable } from "../route-renderer/orchestration/render-output.utils.js";
5
+ } from "../route-renderer/orchestration/foreign-child/component-render-context.js";
6
+ import { isThenable } from "../route-renderer/orchestration/foreign-child/foreign-child-output.utils.js";
7
7
  import { applyPageLayoutConfig, mergeLayoutDependencies, normalizePageLayouts } from "./page-layout-normalization.js";
8
8
  function createComponentFactory(options) {
9
9
  const integrationName = options.integration ?? options.__eco?.integration;
@@ -1 +1 @@
1
- export { addTriggerAttribute, isThenable, wrapWithScriptsInjector, } from '../route-renderer/orchestration/render-output.utils.js';
1
+ export { addTriggerAttribute, isThenable, wrapWithScriptsInjector, } from '../route-renderer/orchestration/foreign-child/foreign-child-output.utils.js';
@@ -2,7 +2,7 @@ import {
2
2
  addTriggerAttribute,
3
3
  isThenable,
4
4
  wrapWithScriptsInjector
5
- } from "../route-renderer/orchestration/render-output.utils.js";
5
+ } from "../route-renderer/orchestration/foreign-child/foreign-child-output.utils.js";
6
6
  export {
7
7
  addTriggerAttribute,
8
8
  isThenable,
@@ -88,7 +88,7 @@ import { getEcoNavigationRuntime } from "../../router/client/navigation-coordina
88
88
  }
89
89
  await new Promise((resolve) => {
90
90
  const startedAt = performance.now();
91
- const timeoutMs = 2e3;
91
+ const timeoutMs = 5e3;
92
92
  const poll = () => {
93
93
  if (!navigationRuntime.hasPendingNavigationTransaction()) {
94
94
  resolve();
@@ -1,5 +1,5 @@
1
1
  import type { EcoPagesAppConfig } from '../types/internal-types.js';
2
- import { type AffectedGraphIdentity } from '../route-renderer/orchestration/page-browser-graph-session.js';
2
+ import { type AffectedGraphIdentity } from '../route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js';
3
3
  export type HmrFileChangePreparation = {
4
4
  affectedGraphIdentities: AffectedGraphIdentity[];
5
5
  invalidatedGraphCount: number;
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import {
3
3
  getAffectedPageBrowserGraphIdentities,
4
4
  invalidatePageBrowserGraphSession
5
- } from "../route-renderer/orchestration/page-browser-graph-session.js";
5
+ } from "../route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js";
6
6
  function prepareHmrFileChange(appConfig, filePath) {
7
7
  const resolvedFilePath = path.resolve(filePath);
8
8
  const affectedGraphIdentities = getAffectedPageBrowserGraphIdentities(appConfig, resolvedFilePath);
@@ -19,6 +19,14 @@ export interface ProcessorWatchContext {
19
19
  }
20
20
  export interface ProcessorWatchConfig {
21
21
  paths: string[];
22
+ /**
23
+ * File extensions that trigger watch callbacks (`onCreate`, `onChange`, `onDelete`).
24
+ *
25
+ * @remarks
26
+ * Watch extensions drive notifications only. They do not declare asset ownership.
27
+ * Ownership requires {@link ProcessorConfig.capabilities} and controls whether dev
28
+ * invalidation skips server modules and HMR for processor-handled assets.
29
+ */
22
30
  extensions?: string[];
23
31
  onCreate?: (ctx: ProcessorWatchContext) => Promise<void>;
24
32
  onChange?: (ctx: ProcessorWatchContext) => Promise<void>;
@@ -85,7 +85,7 @@ flowchart TD
85
85
  B -- Yes --> C[return resolved renderer-owned output]
86
86
  B -- No --> D[queue foreign-subtree token]
87
87
  D --> E[finish current render pass]
88
- E --> F[QueuedForeignSubtreeResolutionService resolveQueuedHtml]
88
+ E --> F[ForeignSubtreeExecutionService resolveQueuedHtml]
89
89
  F --> G[resolve nested queued tokens first]
90
90
  G --> H[dispatch foreign subtree to owning renderer]
91
91
  H --> I[merge emitted assets and root attributes]
@@ -115,12 +115,12 @@ flowchart TD
115
115
  The most useful reading order is:
116
116
 
117
117
  1. `route-renderer.ts`
118
- 2. `orchestration/route-render-orchestrator.ts`
118
+ 2. `orchestration/route-pipeline/route-render-orchestrator.ts`
119
119
  3. `orchestration/integration-renderer.ts`
120
- 4. `orchestration/ownership-validation.service.ts`
121
- 5. `orchestration/component-graph.ts`
122
- 6. `orchestration/component-render-context.ts`
123
- 7. `orchestration/foreign-subtree-execution.service.ts`
120
+ 4. `orchestration/ownership-graph/ownership-validation.service.ts`
121
+ 5. `orchestration/ownership-graph/component-graph.ts`
122
+ 6. `orchestration/foreign-child/component-render-context.ts`
123
+ 7. `orchestration/foreign-child/foreign-subtree-execution.service.ts`
124
124
  8. `page-loading/page-module-loader.ts`
125
125
  9. `page-loading/dependency-resolver.ts`
126
126
  10. `eco/eco.ts`
@@ -128,12 +128,12 @@ The most useful reading order is:
128
128
  ## 7) Key Files
129
129
 
130
130
  - `packages/core/src/route-renderer/route-renderer.ts`
131
- - `packages/core/src/route-renderer/orchestration/route-render-orchestrator.ts`
131
+ - `packages/core/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.ts`
132
132
  - `packages/core/src/route-renderer/orchestration/integration-renderer.ts`
133
- - `packages/core/src/route-renderer/orchestration/ownership-validation.service.ts`
134
- - `packages/core/src/route-renderer/orchestration/component-graph.ts`
135
- - `packages/core/src/route-renderer/orchestration/component-render-context.ts`
136
- - `packages/core/src/route-renderer/orchestration/foreign-subtree-execution.service.ts`
133
+ - `packages/core/src/route-renderer/orchestration/ownership-graph/ownership-validation.service.ts`
134
+ - `packages/core/src/route-renderer/orchestration/ownership-graph/component-graph.ts`
135
+ - `packages/core/src/route-renderer/orchestration/foreign-child/component-render-context.ts`
136
+ - `packages/core/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.ts`
137
137
  - `packages/core/src/route-renderer/page-loading/page-module-loader.ts`
138
138
  - `packages/core/src/route-renderer/page-loading/dependency-resolver.ts`
139
139
  - `packages/core/src/eco/eco.ts`
@@ -50,13 +50,18 @@ See [`../build/README.md`](../build/README.md) for the JSX ownership helper spli
50
50
 
51
51
  ### `orchestration/`
52
52
 
53
- - `route-render-orchestrator.ts`: one route render from page-module loading through final HTML output
53
+ Root renderer classes:
54
+
54
55
  - `integration-renderer.ts`: abstract base class for route rendering, explicit view rendering, and foreign-child delegation
55
- - `ownership-validation.service.ts`: up-front ownership validation for route roots and declared descendants
56
- - `foreign-subtree-execution.service.ts`: foreign-child execution policy, queue token resolution, and `toForeignSubtreeRenderPayload()`
57
- - `document-shell-render.service.ts`: shared page/layout/html shell composition and attribute stamping helpers
58
- - `component-render-context.ts`: active render context used by `eco.component()` to intercept foreign children
59
- - `render-output.utils.ts`: unresolved artifact normalization and marker inspection
56
+ - `string-markup-renderer.ts`: shared base for string-markup integrations
57
+
58
+ Domain folders:
59
+
60
+ - `ownership-graph/`: component graph walk/collect and up-front ownership validation
61
+ - `foreign-child/`: active render context, foreign-subtree execution, deferred template serialization
62
+ - `document-shell/`: shared page/layout/html shell composition and attribute stamping helpers
63
+ - `route-pipeline/`: route preparation, final HTML capture, marker-artifact enforcement
64
+ - `page-browser-graph/`: browser asset graph build, session cache, and injector assets
60
65
 
61
66
  ### `page-loading/`
62
67
 
@@ -1,5 +1,6 @@
1
- import type { BaseIntegrationContext, ComponentRenderInput, ComponentRenderResult, EcoComponent, PageMetadataProps } from '../../types/public-types.js';
2
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
1
+ import type { BaseIntegrationContext, ComponentRenderInput, ComponentRenderResult, EcoComponent, PageMetadataProps } from '../../../types/public-types.js';
2
+ import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
3
+ import { HtmlTransformerService, type HtmlDocumentContribution } from '../../../services/html/html-transformer.service.js';
3
4
  export type DocumentShellLayoutInput = {
4
5
  component: EcoComponent;
5
6
  props?: Record<string, unknown>;
@@ -88,3 +89,19 @@ export declare function applyDocumentShellAttributeStamping(html: string, stampi
88
89
  * Builds the final serialized document HTML for one route page render.
89
90
  */
90
91
  export declare function renderPageDocumentShell(dependencies: DocumentShellRenderDependencies, input: DocumentShellPageRenderInput, docType: string): Promise<string>;
92
+ /**
93
+ * Finalizes already-resolved HTML for explicit renderer-owned paths.
94
+ *
95
+ * @remarks
96
+ * Callers append renderer bootstrap dependencies before invoking this helper.
97
+ * Stamps document/root attributes and runs HTML transformation after nested
98
+ * foreign-subtree resolution without routing back through shared route execution.
99
+ */
100
+ export declare function finalizeDocumentShellHtml(htmlTransformer: HtmlTransformerService, options: {
101
+ html: string;
102
+ partial?: boolean;
103
+ componentRootAttributes?: Record<string, string>;
104
+ documentAttributes?: Record<string, string>;
105
+ transformHtml?: boolean;
106
+ htmlContributions?: HtmlDocumentContribution[];
107
+ }): Promise<string>;
@@ -1,3 +1,6 @@
1
+ import {
2
+ HtmlTransformerService
3
+ } from "../../../services/html/html-transformer.service.js";
1
4
  function resolveDocumentShellLayouts(input) {
2
5
  if (input.layouts && input.layouts.length > 0) {
3
6
  return input.layouts;
@@ -112,9 +115,34 @@ async function renderPageDocumentShell(dependencies, input, docType) {
112
115
  const documentHtml = input.transformDocumentHtml ? input.transformDocumentHtml(composedDocumentHtml) : composedDocumentHtml;
113
116
  return `${docType}${documentHtml}`;
114
117
  }
118
+ async function finalizeDocumentShellHtml(htmlTransformer, options) {
119
+ const html = applyDocumentShellAttributeStamping(
120
+ options.html,
121
+ {
122
+ applyAttributesToFirstBodyElement: (nextHtml, attributes) => htmlTransformer.applyAttributesToFirstBodyElement(nextHtml, attributes),
123
+ applyAttributesToHtmlElement: (nextHtml, attributes) => htmlTransformer.applyAttributesToHtmlElement(nextHtml, attributes)
124
+ },
125
+ {
126
+ componentRootAttributes: options.componentRootAttributes,
127
+ documentAttributes: options.documentAttributes
128
+ }
129
+ );
130
+ const shouldTransform = options.transformHtml ?? !options.partial;
131
+ if (!shouldTransform) {
132
+ return html;
133
+ }
134
+ const transformedResponse = await htmlTransformer.transform(
135
+ new Response(html, {
136
+ headers: { "Content-Type": "text/html" }
137
+ }),
138
+ options.htmlContributions
139
+ );
140
+ return await transformedResponse.text();
141
+ }
115
142
  export {
116
143
  applyDocumentShellAttributeStamping,
117
144
  composeDocumentShell,
118
145
  composeSequentialLayoutChildren,
146
+ finalizeDocumentShellHtml,
119
147
  renderPageDocumentShell
120
148
  };
@@ -1,4 +1,4 @@
1
- import type { EcoPageLayoutEntry, LayoutPropsContext } from '../../types/public-types.js';
1
+ import type { EcoPageLayoutEntry, LayoutPropsContext } from '../../../types/public-types.js';
2
2
  export type LayoutShellPropsContext = LayoutPropsContext & {
3
3
  pageProps?: Record<string, unknown>;
4
4
  };
@@ -1,4 +1,4 @@
1
- import type { EcoComponent } from '../../types/public-types.js';
1
+ import type { EcoComponent } from '../../../types/public-types.js';
2
2
  /**
3
3
  * Result returned by a renderer-owned foreign-child runtime.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { addTriggerAttribute, isThenable, wrapWithScriptsInjector } from "./render-output.utils.js";
1
+ import { addTriggerAttribute, isThenable, wrapWithScriptsInjector } from "./foreign-child-output.utils.js";
2
2
  class ComponentRenderOutputRuntime {
3
3
  finalizeComponentRender(component, content) {
4
4
  const lazyTriggers = component.config?._resolvedLazyTriggers;
@@ -1,4 +1,4 @@
1
- import type { EcoComponent } from '../../types/public-types.js';
1
+ import type { EcoComponent } from '../../../types/public-types.js';
2
2
  type ResolvedLazyScriptGroups = Readonly<NonNullable<EcoComponent['config']>['_resolvedLazyScripts']>;
3
3
  type TemplateContentShape = {
4
4
  strings: readonly string[];
@@ -73,10 +73,4 @@ export declare function wrapWithScriptsInjector(content: string, lazyGroups: Res
73
73
  export declare function wrapWithScriptsInjector<T extends TemplateContentShape>(content: T, lazyGroups: ResolvedLazyScriptGroups): T | string;
74
74
  export declare function wrapWithScriptsInjector<T extends MarkupNodeLikeShape>(content: T, lazyGroups: ResolvedLazyScriptGroups): T;
75
75
  export declare function wrapWithScriptsInjector(content: unknown, lazyGroups: ResolvedLazyScriptGroups): string | TemplateContentShape | MarkupNodeLikeShape;
76
- export declare function decodeHtmlEntities(value: string): string;
77
- export declare function normalizeUnresolvedMarkerArtifactHtml(html: string): string;
78
- export declare function inspectUnresolvedMarkerArtifactHtml(html: string): {
79
- hasUnresolvedMarkerArtifacts: boolean;
80
- normalizedHtml: string;
81
- };
82
76
  export {};
@@ -1,4 +1,4 @@
1
- import { buildInjectorMapScript } from "../../eco/lazy-injector-map.js";
1
+ import { buildInjectorMapScript } from "../../../eco/lazy-injector-map.js";
2
2
  function cloneTemplateStrings(strings, firstString) {
3
3
  const nextStrings = [...strings];
4
4
  nextStrings[0] = firstString;
@@ -170,36 +170,11 @@ function wrapWithScriptsInjector(content, lazyGroups) {
170
170
  }
171
171
  return wrapWithScriptsInjectorMarkup(String(content), injectorMapScript);
172
172
  }
173
- function decodeHtmlEntities(value) {
174
- let decoded = value;
175
- let previous;
176
- do {
177
- previous = decoded;
178
- decoded = decoded.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&#x27;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
179
- } while (decoded !== previous);
180
- return decoded;
181
- }
182
- function normalizeUnresolvedMarkerArtifactHtml(html) {
183
- return html.replace(
184
- /&(?:amp;)?lt;eco-marker\b[\s\S]*?&(?:amp;)?gt;&(?:amp;)?lt;\/eco-marker&(?:amp;)?gt;/g,
185
- (marker) => decodeHtmlEntities(marker)
186
- );
187
- }
188
- function inspectUnresolvedMarkerArtifactHtml(html) {
189
- const normalizedHtml = normalizeUnresolvedMarkerArtifactHtml(html);
190
- return {
191
- normalizedHtml,
192
- hasUnresolvedMarkerArtifacts: normalizedHtml.includes("<eco-marker")
193
- };
194
- }
195
173
  export {
196
174
  addTriggerAttribute,
197
175
  assertForeignChildrenNotOpaque,
198
- decodeHtmlEntities,
199
- inspectUnresolvedMarkerArtifactHtml,
200
176
  isMarkupNodeLike,
201
177
  isOpaqueForeignChildValue,
202
178
  isThenable,
203
- normalizeUnresolvedMarkerArtifactHtml,
204
179
  wrapWithScriptsInjector
205
180
  };
@@ -1,5 +1,5 @@
1
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
2
- import type { BaseIntegrationContext, ComponentRenderInput, ComponentRenderResult, EcoComponent, ForeignSubtreeRenderPayload } from '../../types/public-types.js';
1
+ import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
2
+ import type { BaseIntegrationContext, ComponentRenderInput, ComponentRenderResult, EcoComponent, ForeignSubtreeRenderPayload } from '../../../types/public-types.js';
3
3
  import { type ForeignChildRuntime } from './component-render-context.js';
4
4
  export type QueuedForeignChildDecisionInput = {
5
5
  currentIntegration: string;
@@ -46,7 +46,7 @@ export interface ForeignSubtreeExecutionRenderOptions {
46
46
  renderInput: ComponentRenderInput;
47
47
  rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>;
48
48
  }): ForeignChildRuntime;
49
- getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): ForeignSubtreeExecutionOwningRenderer;
49
+ getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): Promise<ForeignSubtreeExecutionOwningRenderer>;
50
50
  }
51
51
  export interface ForeignSubtreeQueuedRuntimeOptions<TContext extends QueuedForeignSubtreeResolutionContext> {
52
52
  renderInput: ComponentRenderInput;
@@ -61,7 +61,7 @@ export interface ForeignSubtreeStringQueuedHtmlOptions {
61
61
  html: string;
62
62
  runtimeContextKey: string;
63
63
  queueLabel: string;
64
- getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): ForeignSubtreeExecutionOwningRenderer;
64
+ getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): Promise<ForeignSubtreeExecutionOwningRenderer>;
65
65
  applyAttributesToFirstElement(html: string, attributes: Record<string, string>): string;
66
66
  dedupeProcessedAssets(assets: ProcessedAsset[]): ProcessedAsset[];
67
67
  }
@@ -75,7 +75,7 @@ export interface ForeignSubtreeQueuedHtmlOptions<TContext extends QueuedForeignS
75
75
  children?: unknown;
76
76
  html?: string;
77
77
  }>;
78
- getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): ForeignSubtreeExecutionOwningRenderer;
78
+ getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): Promise<ForeignSubtreeExecutionOwningRenderer>;
79
79
  applyAttributesToFirstElement(html: string, attributes: Record<string, string>): string;
80
80
  dedupeProcessedAssets(assets: ProcessedAsset[]): ProcessedAsset[];
81
81
  }
@@ -2,7 +2,7 @@ import {
2
2
  getComponentRenderContext,
3
3
  runWithComponentRenderContext
4
4
  } from "./component-render-context.js";
5
- import { assertForeignChildrenNotOpaque, isMarkupNodeLike } from "./render-output.utils.js";
5
+ import { assertForeignChildrenNotOpaque, isMarkupNodeLike } from "./foreign-child-output.utils.js";
6
6
  function toForeignSubtreeRenderPayload(result) {
7
7
  return {
8
8
  html: result.html,
@@ -313,7 +313,7 @@ class ForeignSubtreeExecutionService {
313
313
  if (!foreignOwnerIntegrationName) {
314
314
  return void 0;
315
315
  }
316
- const owningRenderer = options.getOwningRenderer(foreignOwnerIntegrationName, options.rendererCache);
316
+ const owningRenderer = await options.getOwningRenderer(foreignOwnerIntegrationName, options.rendererCache);
317
317
  if (owningRenderer.name === options.currentIntegrationName) {
318
318
  return void 0;
319
319
  }
@@ -0,0 +1,19 @@
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { ForeignSubtreeExecutionOwningRenderer } from './foreign-subtree-execution.service.js';
3
+ export type OwningRendererResolutionInput = {
4
+ appConfig: EcoPagesAppConfig;
5
+ runtimeOrigin: string;
6
+ currentIntegrationName: string;
7
+ currentRenderer: ForeignSubtreeExecutionOwningRenderer;
8
+ integrationName: string;
9
+ cache: Map<string, ForeignSubtreeExecutionOwningRenderer>;
10
+ };
11
+ export declare function getForeignSubtreeTokenPrefix(integrationName: string): string;
12
+ export declare function getForeignSubtreeResolutionContextKey(integrationName: string): string;
13
+ /**
14
+ * Resolves an owning integration renderer for a foreign-child handoff.
15
+ *
16
+ * Reuses the execution-scoped cache, activates the target runtime when needed,
17
+ * and initializes the plugin renderer once per integration name.
18
+ */
19
+ export declare function resolveOwningIntegrationRenderer(input: OwningRendererResolutionInput): Promise<ForeignSubtreeExecutionOwningRenderer>;
@@ -0,0 +1,35 @@
1
+ import { ensureIntegrationRuntimeReady } from "../../../build/app-build-manifest-runtime.js";
2
+ import { invariant } from "../../../utils/invariant.js";
3
+ function getForeignSubtreeTokenPrefix(integrationName) {
4
+ return `__${integrationName}_foreign_subtree__`;
5
+ }
6
+ function getForeignSubtreeResolutionContextKey(integrationName) {
7
+ return `__${integrationName}_foreign_subtree_runtime__`;
8
+ }
9
+ async function resolveOwningIntegrationRenderer(input) {
10
+ const { integrationName, cache } = input;
11
+ if (cache.has(integrationName)) {
12
+ return cache.get(integrationName);
13
+ }
14
+ if (integrationName === input.currentIntegrationName) {
15
+ cache.set(integrationName, input.currentRenderer);
16
+ return input.currentRenderer;
17
+ }
18
+ await ensureIntegrationRuntimeReady({
19
+ appConfig: input.appConfig,
20
+ integrationName,
21
+ runtimeOrigin: input.runtimeOrigin
22
+ });
23
+ const integrationPlugin = input.appConfig.integrations.find((integration) => integration.name === integrationName);
24
+ invariant(!!integrationPlugin, `[ecopages] Integration not found for foreign owner: ${integrationName}`);
25
+ const renderer = integrationPlugin.initializeRenderer({
26
+ rendererModules: input.appConfig.runtime?.rendererModuleContext
27
+ });
28
+ cache.set(integrationName, renderer);
29
+ return renderer;
30
+ }
31
+ export {
32
+ getForeignSubtreeResolutionContextKey,
33
+ getForeignSubtreeTokenPrefix,
34
+ resolveOwningIntegrationRenderer
35
+ };
@@ -4,16 +4,16 @@
4
4
  * @module
5
5
  */
6
6
  import type { EcoPagesAppConfig, IHmrManager } from '../../types/internal-types.js';
7
- import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPageFile, EcoPagesElement, BaseIntegrationContext, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PageBrowserGraphResult, PageMetadataProps, PagePackageResult, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
7
+ import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, 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';
11
11
  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
- import { RouteRenderOrchestrator, type RouteRenderOrchestratorAdapter, type RouteRenderOrchestratorResolvedInputs } from './route-render-orchestrator.js';
15
- import type { ForeignChildRuntime } from './component-render-context.js';
16
- import { ForeignSubtreeExecutionService, type ForeignSubtreeExecutionOwningRenderer, type ForeignSubtreeQueuedHtmlOptions, type QueuedForeignSubtreeResolutionContext } from './foreign-subtree-execution.service.js';
14
+ import { RouteRenderOrchestrator, type RouteRenderOrchestratorAdapter, type RouteRenderOrchestratorResolvedInputs } from './route-pipeline/route-render-orchestrator.js';
15
+ import type { ForeignChildRuntime } from './foreign-child/component-render-context.js';
16
+ import { ForeignSubtreeExecutionService } from './foreign-child/foreign-subtree-execution.service.js';
17
17
  /**
18
18
  * Controls how one route module is loaded outside the normal render path.
19
19
  *
@@ -118,7 +118,6 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
118
118
  */
119
119
  protected prepareViewDependencies(view: EcoComponent, layout?: EcoComponent): Promise<ProcessedAsset[]>;
120
120
  protected resolvePageBrowserGraphForFile(filePath: string): Promise<PageBrowserGraphResult | undefined>;
121
- protected mergePageBrowserGraphIntoPagePackage(pageBrowserGraph?: PageBrowserGraphResult): PageBrowserGraphResult | undefined;
122
121
  /**
123
122
  * Merges component-scoped assets into the active HTML transformer state.
124
123
  *
@@ -208,20 +207,6 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
208
207
  transformDocumentHtml?: (html: string) => string;
209
208
  }): Promise<string>;
210
209
  protected renderStringComponentWithSerializedChildren(input: ComponentRenderInput, component: (props: Record<string, unknown>) => Promise<EcoPagesElement> | EcoPagesElement): Promise<ComponentRenderResult>;
211
- protected getForeignSubtreeTokenPrefix(): string;
212
- protected getForeignSubtreeResolutionContextKey(): string;
213
- protected createQueuedForeignSubtreeExecutionRuntime<TContext extends QueuedForeignSubtreeResolutionContext>(options: {
214
- renderInput: ComponentRenderInput;
215
- rendererCache: Map<string, IntegrationRenderer<any>>;
216
- runtimeContextKey?: string;
217
- tokenPrefix?: string;
218
- createRuntimeContext?: (integrationContext: BaseIntegrationContext & Record<string, unknown>, rendererCache: Map<string, unknown>) => TContext;
219
- }): ForeignChildRuntime;
220
- protected getQueuedForeignSubtreeResolutionContext<TContext extends QueuedForeignSubtreeResolutionContext>(input: ComponentRenderInput): TContext | undefined;
221
- protected resolveQueuedForeignSubtreeHtml<TContext extends QueuedForeignSubtreeResolutionContext>(html: string, runtimeContext: TContext | undefined, renderQueuedChildren: ForeignSubtreeQueuedHtmlOptions<TContext>['renderQueuedChildren'], queueLabel?: string): Promise<{
222
- assets: ProcessedAsset[];
223
- html: string;
224
- }>;
225
210
  /**
226
211
  * Renders a string-first component, then resolves any queued foreign
227
212
  * boundaries before returning final component HTML.
@@ -291,7 +276,6 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
291
276
  resolvedDependencies: ProcessedAsset[];
292
277
  }>;
293
278
  protected renderRouteBody(renderOptions: IntegrationRendererRenderOptions<C>): Promise<RouteRendererBody>;
294
- protected transformRouteResponse(response: Response, htmlContributions?: HtmlDocumentContribution[], pagePackage?: PagePackageResult): Promise<RouteRendererBody>;
295
279
  /**
296
280
  * Prepares the render options for the integration renderer.
297
281
  * It imports the page file, collects dependencies, and prepares the render options.
@@ -318,21 +302,6 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
318
302
  * @returns Rendered route body plus effective cache strategy.
319
303
  */
320
304
  execute(options: RouteRendererOptions): Promise<RouteRenderResult>;
321
- /**
322
- * Finalizes already-resolved HTML for explicit renderer-owned paths.
323
- *
324
- * This keeps document and root-attribute stamping plus HTML transformation
325
- * available after a renderer has completed nested foreign-subtree resolution without
326
- * routing back through shared route execution.
327
- */
328
- protected finalizeResolvedHtml(options: {
329
- html: string;
330
- partial?: boolean;
331
- componentRootAttributes?: Record<string, string>;
332
- documentAttributes?: Record<string, string>;
333
- transformHtml?: boolean;
334
- htmlContributions?: HtmlDocumentContribution[];
335
- }): Promise<string>;
336
305
  /**
337
306
  * Returns document-level attributes to stamp onto the rendered `<html>` tag.
338
307
  *
@@ -352,17 +321,6 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
352
321
  * response finalization logic.
353
322
  */
354
323
  protected getHtmlDocumentContributions(_options: HtmlDocumentContributionContext<C>): HtmlDocumentContribution[] | undefined;
355
- /**
356
- * Returns a renderer instance for a given integration name.
357
- *
358
- * Uses a per-execution cache to avoid repeated renderer initialization.
359
- *
360
- * @param integrationName Target integration name.
361
- * @param cache Render-pass renderer cache.
362
- * @returns Renderer for the requested integration.
363
- * @throws Error when no integration plugin matches `integrationName`.
364
- */
365
- protected getIntegrationRendererForName(integrationName: string, cache: Map<string, ForeignSubtreeExecutionOwningRenderer>): ForeignSubtreeExecutionOwningRenderer;
366
324
  /**
367
325
  * Abstract method to render the integration-specific component.
368
326
  * This method should be implemented by the specific integration renderer.