@ecopages/core 0.2.0-alpha.25 → 0.2.0-alpha.27
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/README.md +63 -7
- package/package.json +4 -47
- package/src/adapters/bun/create-app.ts +54 -2
- package/src/adapters/bun/hmr-manager.test.ts +0 -2
- package/src/adapters/bun/hmr-manager.ts +1 -24
- package/src/adapters/bun/server-adapter.ts +30 -4
- package/src/adapters/node/node-hmr-manager.test.ts +0 -2
- package/src/adapters/node/node-hmr-manager.ts +2 -25
- package/src/adapters/shared/explicit-static-render-preparation.ts +58 -0
- package/src/adapters/shared/explicit-static-route-matcher.test.ts +6 -6
- package/src/adapters/shared/explicit-static-route-matcher.ts +22 -31
- package/src/adapters/shared/file-route-middleware-pipeline.test.ts +5 -10
- package/src/adapters/shared/file-route-middleware-pipeline.ts +8 -17
- package/src/adapters/shared/fs-server-response-factory.test.ts +32 -43
- package/src/adapters/shared/fs-server-response-factory.ts +15 -37
- package/src/adapters/shared/fs-server-response-matcher.test.ts +65 -39
- package/src/adapters/shared/fs-server-response-matcher.ts +94 -43
- package/src/adapters/shared/hmr-manager.contract.test.ts +0 -4
- package/src/adapters/shared/render-context.ts +3 -3
- package/src/adapters/shared/server-adapter.test.ts +53 -0
- package/src/adapters/shared/server-adapter.ts +228 -159
- package/src/adapters/shared/server-route-handler.test.ts +6 -5
- package/src/adapters/shared/server-route-handler.ts +4 -4
- package/src/adapters/shared/server-static-builder.test.ts +4 -4
- package/src/adapters/shared/server-static-builder.ts +4 -4
- package/src/config/README.md +1 -1
- package/src/config/config-builder.test.ts +0 -1
- package/src/config/config-builder.ts +2 -7
- package/src/dev/host-runtime.ts +34 -0
- package/src/eco/eco.browser.test.ts +2 -2
- package/src/eco/eco.browser.ts +2 -2
- package/src/eco/eco.test.ts +6 -6
- package/src/eco/eco.ts +12 -12
- package/src/eco/eco.types.ts +3 -3
- package/src/errors/index.ts +1 -0
- package/src/hmr/client/hmr-runtime.ts +4 -2
- package/src/hmr/strategies/js-hmr-strategy.test.ts +0 -1
- package/src/hmr/strategies/js-hmr-strategy.ts +0 -6
- package/src/integrations/ghtml/ghtml-renderer.test.ts +7 -7
- package/src/integrations/ghtml/ghtml-renderer.ts +1 -11
- package/src/plugins/eco-component-meta-plugin.ts +0 -1
- package/src/plugins/integration-plugin.test.ts +9 -14
- package/src/plugins/integration-plugin.ts +34 -22
- package/src/plugins/processor.ts +17 -0
- package/src/route-renderer/GRAPH.md +81 -289
- package/src/route-renderer/README.md +67 -105
- package/src/route-renderer/orchestration/component-render-context.ts +45 -38
- package/src/route-renderer/orchestration/declared-ownership-graph.ts +62 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.ts +383 -0
- package/src/route-renderer/orchestration/integration-renderer.test.ts +118 -121
- package/src/route-renderer/orchestration/integration-renderer.ts +362 -403
- package/src/route-renderer/orchestration/ownership-planning.service.ts +97 -0
- package/src/route-renderer/orchestration/ownership-validation.service.ts +76 -0
- package/src/route-renderer/orchestration/processed-asset-dedupe.ts +1 -1
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.test.ts → queued-foreign-subtree-resolution.service.test.ts} +76 -71
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.ts → queued-foreign-subtree-resolution.service.ts} +68 -63
- package/src/route-renderer/orchestration/render-output.utils.ts +21 -13
- package/src/route-renderer/orchestration/{render-preparation.service.test.ts → route-render-orchestrator.prepare-render-options.test.ts} +160 -85
- package/src/route-renderer/orchestration/route-render-orchestrator.test.ts +265 -0
- package/src/route-renderer/orchestration/{render-preparation.service.ts → route-render-orchestrator.ts} +244 -160
- package/src/route-renderer/page-loading/component-dependency-collection.ts +9 -3
- package/src/route-renderer/page-loading/declared-asset-collection.ts +2 -5
- package/src/route-renderer/page-loading/dependency-resolver.test.ts +107 -11
- package/src/route-renderer/page-loading/dependency-resolver.ts +6 -12
- package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +1 -1
- package/src/route-renderer/page-loading/lazy-entry-collection.ts +1 -1
- package/src/route-renderer/page-loading/lazy-trigger-planning.ts +1 -1
- package/src/route-renderer/page-loading/module-declaration-aggregation.ts +1 -1
- package/src/route-renderer/page-loading/module-declaration-scripts.ts +1 -1
- package/src/route-renderer/page-loading/page-dependency-bundling.ts +105 -66
- package/src/route-renderer/route-renderer.ts +28 -31
- package/src/router/README.md +16 -19
- package/src/router/server/route-registry.test.ts +176 -0
- package/src/router/server/route-registry.ts +382 -0
- package/src/services/README.md +1 -2
- package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +1 -1
- package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +1 -4
- package/src/services/assets/asset-processing-service/asset-processing.service.ts +1 -2
- package/src/services/assets/asset-processing-service/assets.types.ts +3 -0
- package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +1 -1
- package/src/services/assets/asset-processing-service/index.ts +1 -0
- package/src/{route-renderer/orchestration/page-packaging.service.test.ts → services/assets/asset-processing-service/page-package.test.ts} +38 -14
- package/src/services/assets/asset-processing-service/page-package.ts +93 -0
- package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +4 -5
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +13 -10
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +3 -0
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +6 -0
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +2 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +1 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +2 -0
- package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +1 -1
- package/src/services/html/html-transformer.service.test.ts +1 -4
- package/src/services/module-loading/app-server-module-transpiler.service.ts +1 -3
- package/src/services/module-loading/node-bootstrap-plugin.ts +17 -3
- package/src/services/module-loading/page-module-import.service.ts +0 -1
- package/src/services/module-loading/source-module-support.ts +1 -1
- package/src/static-site-generator/static-site-generator.test.ts +124 -32
- package/src/static-site-generator/static-site-generator.ts +168 -185
- package/src/types/internal-types.ts +13 -12
- package/src/types/public-types.ts +55 -39
- package/src/watchers/project-watcher.test-helpers.ts +4 -3
- package/src/route-renderer/orchestration/boundary-planning.service.ts +0 -146
- package/src/route-renderer/orchestration/page-packaging.service.ts +0 -85
- package/src/route-renderer/orchestration/render-execution.service.test.ts +0 -196
- package/src/route-renderer/orchestration/render-execution.service.ts +0 -182
- package/src/route-renderer/orchestration/route-shell-composer.service.ts +0 -162
- package/src/router/server/fs-router-scanner.test.ts +0 -83
- package/src/router/server/fs-router-scanner.ts +0 -224
- package/src/router/server/fs-router.test.ts +0 -214
- package/src/router/server/fs-router.ts +0 -122
- package/src/services/runtime-state/runtime-specifier-registry.service.ts +0 -96
|
@@ -3,66 +3,57 @@ import path from 'node:path';
|
|
|
3
3
|
import type { EcoPagesAppConfig } from '../../types/internal-types.ts';
|
|
4
4
|
import type {
|
|
5
5
|
ComponentRenderResult,
|
|
6
|
+
DependencyAttributes,
|
|
6
7
|
EcoComponent,
|
|
7
8
|
EcoComponentConfig,
|
|
8
|
-
DependencyAttributes,
|
|
9
9
|
EcoPageComponent,
|
|
10
10
|
EcoPageFile,
|
|
11
|
-
EcoPagesElement,
|
|
12
|
-
GetMetadata,
|
|
13
|
-
GetStaticProps,
|
|
14
11
|
HtmlTemplateProps,
|
|
15
12
|
IntegrationRendererRenderOptions,
|
|
16
|
-
|
|
13
|
+
PageBrowserGraphResult,
|
|
17
14
|
PageMetadataProps,
|
|
15
|
+
PageProps,
|
|
18
16
|
ResolvedLazyTrigger,
|
|
17
|
+
RouteRendererBody,
|
|
19
18
|
RouteRendererOptions,
|
|
19
|
+
RouteRenderResult,
|
|
20
20
|
} from '../../types/public-types.ts';
|
|
21
21
|
import {
|
|
22
22
|
type AssetProcessingService,
|
|
23
23
|
AssetFactory,
|
|
24
|
+
createPagePackage,
|
|
24
25
|
type ProcessedAsset,
|
|
25
26
|
} from '../../services/assets/asset-processing-service/index.ts';
|
|
26
27
|
import { buildGlobalInjectorBootstrapContent, buildGlobalInjectorMapScript } from '../../eco/global-injector-map.ts';
|
|
27
28
|
import { LocalsAccessError } from '../../errors/locals-access-error.ts';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
29
|
+
import { inspectUnresolvedMarkerArtifactHtml } from './render-output.utils.ts';
|
|
30
|
+
import { OwnershipValidationService } from './ownership-validation.service.ts';
|
|
31
|
+
import { OwnershipPlanningService } from './ownership-planning.service.ts';
|
|
30
32
|
import { dedupeProcessedAssets } from './processed-asset-dedupe.ts';
|
|
31
33
|
|
|
32
|
-
type
|
|
34
|
+
export type RouteRenderOrchestratorResolvedInputs = {
|
|
33
35
|
Page: EcoPageFile['default'] | EcoPageComponent<any>;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
HtmlTemplate: EcoComponent<HtmlTemplateProps>;
|
|
37
|
+
Layout?: EcoComponent;
|
|
38
|
+
props: Record<string, unknown>;
|
|
39
|
+
metadata: PageMetadataProps;
|
|
36
40
|
integrationSpecificProps: Record<string, unknown>;
|
|
37
41
|
};
|
|
38
42
|
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
pageModule: {
|
|
44
|
-
getStaticProps?: GetStaticProps<Record<string, unknown>>;
|
|
45
|
-
getMetadata?: GetMetadata;
|
|
46
|
-
},
|
|
47
|
-
routeOptions: RouteRendererOptions,
|
|
48
|
-
): Promise<{ props: Record<string, unknown>; metadata: PageMetadataProps }>;
|
|
49
|
-
resolveDependencies(components: (EcoComponent | Partial<EcoComponent>)[]): Promise<ProcessedAsset[]>;
|
|
50
|
-
buildRouteRenderAssets(file: string): Promise<ProcessedAsset[]> | undefined;
|
|
51
|
-
shouldRenderPageComponent(input: {
|
|
52
|
-
Page: EcoComponent;
|
|
53
|
-
Layout?: EcoComponent;
|
|
54
|
-
options: RouteRendererOptions;
|
|
55
|
-
}): boolean;
|
|
56
|
-
renderPageComponent(input: {
|
|
57
|
-
component: EcoComponent;
|
|
58
|
-
props: Record<string, unknown>;
|
|
59
|
-
}): Promise<ComponentRenderResult>;
|
|
60
|
-
}
|
|
43
|
+
export type RouteRenderOrchestratorResolvedAssets = {
|
|
44
|
+
resolvedDependencies: ProcessedAsset[];
|
|
45
|
+
pageBrowserGraph?: PageBrowserGraphResult;
|
|
46
|
+
};
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Structural HTML work applied after the route body has been fully resolved.
|
|
50
|
+
*
|
|
51
|
+
* The shared route flow only needs to know whether a post-render HTML step
|
|
52
|
+
* exists. When `finalizeHtml` is absent, the captured body can be reused as-is.
|
|
53
|
+
*/
|
|
54
|
+
export type RouteHtmlFinalization = {
|
|
55
|
+
finalizeHtml?(html: string): string;
|
|
56
|
+
};
|
|
66
57
|
|
|
67
58
|
function createPageLocalsProxy(filePath: string): Record<string, never> {
|
|
68
59
|
const errorMessage = `[ecopages] Request locals are only available during request-time rendering with cache: 'dynamic'. Page: ${filePath}. If you meant to use locals here, set cache: 'dynamic' and provide locals from route middleware/handlers.`;
|
|
@@ -95,114 +86,158 @@ function createPageLocalsProxy(filePath: string): Record<string, never> {
|
|
|
95
86
|
);
|
|
96
87
|
}
|
|
97
88
|
|
|
89
|
+
export interface RouteRenderOrchestratorAdapter<C> {
|
|
90
|
+
/**
|
|
91
|
+
* Name of the owning Integration for the current route render.
|
|
92
|
+
*/
|
|
93
|
+
readonly name: string;
|
|
94
|
+
/**
|
|
95
|
+
* Loads the Integration-owned route inputs needed for one Page render.
|
|
96
|
+
*/
|
|
97
|
+
resolveRouteRenderInputs(routeOptions: RouteRendererOptions): Promise<RouteRenderOrchestratorResolvedInputs>;
|
|
98
|
+
/**
|
|
99
|
+
* Resolves route-owned assets needed before Integration rendering starts.
|
|
100
|
+
*/
|
|
101
|
+
resolveRouteAssets(input: {
|
|
102
|
+
routeOptions: RouteRendererOptions;
|
|
103
|
+
components: (EcoComponent | Partial<EcoComponent>)[];
|
|
104
|
+
}): Promise<RouteRenderOrchestratorResolvedAssets>;
|
|
105
|
+
/**
|
|
106
|
+
* Resolves the optional page-root render through the foreign-child-aware component contract.
|
|
107
|
+
*/
|
|
108
|
+
resolveRoutePageComponentRender(input: {
|
|
109
|
+
Page: EcoComponent;
|
|
110
|
+
Layout?: EcoComponent;
|
|
111
|
+
props: Record<string, unknown>;
|
|
112
|
+
routeOptions: RouteRendererOptions;
|
|
113
|
+
}): Promise<ComponentRenderResult | undefined>;
|
|
114
|
+
/**
|
|
115
|
+
* Executes the Integration-specific route render.
|
|
116
|
+
*/
|
|
117
|
+
renderRouteBody(renderOptions: IntegrationRendererRenderOptions<C>): Promise<RouteRendererBody>;
|
|
118
|
+
/**
|
|
119
|
+
* Returns the structural Html finalization plan for one prepared route render.
|
|
120
|
+
*/
|
|
121
|
+
getRouteHtmlFinalization(renderOptions: IntegrationRendererRenderOptions<C>): RouteHtmlFinalization;
|
|
122
|
+
/**
|
|
123
|
+
* Runs SSR-policy response transformation and returns the body value exposed to callers.
|
|
124
|
+
*/
|
|
125
|
+
transformRouteResponse(response: Response): Promise<RouteRendererBody>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Captured route-render output in both replayable body and string HTML forms.
|
|
130
|
+
*/
|
|
131
|
+
export interface CapturedHtmlRenderResult {
|
|
132
|
+
body: RouteRendererBody;
|
|
133
|
+
html: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Optional app-scoped collaborators used by the route render orchestrator.
|
|
138
|
+
*/
|
|
139
|
+
export interface RouteRenderOrchestratorDependencies {
|
|
140
|
+
ownershipPlanningService?: OwnershipPlanningService;
|
|
141
|
+
ownershipValidationService?: OwnershipValidationService;
|
|
142
|
+
}
|
|
143
|
+
|
|
98
144
|
/**
|
|
99
|
-
*
|
|
145
|
+
* Owns one route render from normalized module loading through final HTML output.
|
|
100
146
|
*
|
|
101
|
-
* This
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
147
|
+
* This orchestrator keeps route rendering as one app-scoped unit while still
|
|
148
|
+
* delegating integration-specific behavior through the adapter seam. It owns
|
|
149
|
+
* route-root validation, dependency aggregation, page package creation, and the
|
|
150
|
+
* final HTML/body handling that happens after the integration render returns.
|
|
105
151
|
*/
|
|
106
|
-
export class
|
|
107
|
-
private appConfig: EcoPagesAppConfig;
|
|
108
|
-
private assetProcessingService: AssetProcessingService;
|
|
109
|
-
private readonly
|
|
110
|
-
private readonly
|
|
152
|
+
export class RouteRenderOrchestrator {
|
|
153
|
+
private readonly appConfig: EcoPagesAppConfig;
|
|
154
|
+
private readonly assetProcessingService: AssetProcessingService;
|
|
155
|
+
private readonly ownershipPlanningService: OwnershipPlanningService;
|
|
156
|
+
private readonly ownershipValidationService: OwnershipValidationService;
|
|
111
157
|
|
|
112
|
-
/**
|
|
113
|
-
* Creates the render-preparation orchestrator for one app instance.
|
|
114
|
-
*
|
|
115
|
-
* @remarks
|
|
116
|
-
* The service is app-scoped because it depends on finalized config defaults and
|
|
117
|
-
* the app-owned asset-processing pipeline while remaining renderer-agnostic.
|
|
118
|
-
*/
|
|
119
158
|
constructor(
|
|
120
159
|
appConfig: EcoPagesAppConfig,
|
|
121
160
|
assetProcessingService: AssetProcessingService,
|
|
122
|
-
dependencies:
|
|
161
|
+
dependencies: RouteRenderOrchestratorDependencies = {},
|
|
123
162
|
) {
|
|
124
163
|
this.appConfig = appConfig;
|
|
125
164
|
this.assetProcessingService = assetProcessingService;
|
|
126
|
-
this.
|
|
127
|
-
this.
|
|
165
|
+
this.ownershipPlanningService = dependencies.ownershipPlanningService ?? new OwnershipPlanningService();
|
|
166
|
+
this.ownershipValidationService =
|
|
167
|
+
dependencies.ownershipValidationService ?? new OwnershipValidationService(appConfig);
|
|
128
168
|
}
|
|
129
169
|
|
|
130
170
|
/**
|
|
131
|
-
* Builds
|
|
132
|
-
* renderer.
|
|
171
|
+
* Builds normalized route render options before the integration render runs.
|
|
133
172
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* @typeParam C Integration render output element type.
|
|
139
|
-
* @param routeOptions Route-level render inputs.
|
|
140
|
-
* @param currentIntegrationName Active integration name for this preparation pass.
|
|
141
|
-
* @param callbacks Renderer-specific hooks used during preparation.
|
|
142
|
-
* @returns Normalized render options.
|
|
173
|
+
* This preparation step validates route-root ownership, resolves page data,
|
|
174
|
+
* collects processed assets, captures optional page-root render metadata, and
|
|
175
|
+
* produces the page package consumed by downstream HTML transformation.
|
|
143
176
|
*/
|
|
144
|
-
async
|
|
177
|
+
async prepareRenderOptions<C = unknown>(
|
|
145
178
|
routeOptions: RouteRendererOptions,
|
|
146
|
-
|
|
147
|
-
callbacks: RenderPreparationCallbacks,
|
|
179
|
+
adapter: RouteRenderOrchestratorAdapter<C>,
|
|
148
180
|
): Promise<IntegrationRendererRenderOptions<C>> {
|
|
149
|
-
const
|
|
150
|
-
const { Page, integrationSpecificProps } =
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
181
|
+
const resolvedInputs = await adapter.resolveRouteRenderInputs(routeOptions);
|
|
182
|
+
const { Page, HtmlTemplate, Layout, props, metadata, integrationSpecificProps } = resolvedInputs;
|
|
183
|
+
const validationErrors = this.ownershipValidationService.validate({
|
|
184
|
+
currentIntegrationName: adapter.name,
|
|
185
|
+
roots: [
|
|
186
|
+
{ component: HtmlTemplate as EcoComponent, source: 'html-template' },
|
|
187
|
+
...(Layout ? [{ component: Layout as EcoComponent, source: 'layout' as const }] : []),
|
|
188
|
+
{ component: Page as EcoComponent, source: 'page' },
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
const ownershipPlan = this.ownershipPlanningService.buildPlan({
|
|
155
192
|
routeFile: routeOptions.file,
|
|
156
|
-
currentIntegrationName,
|
|
157
|
-
HtmlTemplate,
|
|
193
|
+
currentIntegrationName: adapter.name,
|
|
194
|
+
HtmlTemplate: HtmlTemplate as EcoComponent,
|
|
158
195
|
Layout,
|
|
159
196
|
Page: Page as EcoComponent,
|
|
197
|
+
validationErrors,
|
|
160
198
|
});
|
|
161
199
|
|
|
162
200
|
const componentsToResolve = Layout ? [HtmlTemplate, Layout, Page] : [HtmlTemplate, Page];
|
|
163
|
-
const resolvedDependencies = await
|
|
164
|
-
|
|
165
|
-
componentsToResolve,
|
|
166
|
-
|
|
167
|
-
);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
201
|
+
const { resolvedDependencies, pageBrowserGraph } = await adapter.resolveRouteAssets({
|
|
202
|
+
routeOptions,
|
|
203
|
+
components: componentsToResolve,
|
|
204
|
+
});
|
|
205
|
+
const usedIntegrationDependencies = this.collectUsedIntegrationDependencies(componentsToResolve, adapter.name);
|
|
206
|
+
const allDependencies = [
|
|
207
|
+
...resolvedDependencies,
|
|
208
|
+
...usedIntegrationDependencies,
|
|
209
|
+
...(pageBrowserGraph?.assets ?? []),
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
const componentRender = await adapter.resolveRoutePageComponentRender({
|
|
213
|
+
Page: Page as EcoComponent,
|
|
214
|
+
Layout,
|
|
215
|
+
props,
|
|
216
|
+
routeOptions,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (componentRender?.assets?.length) {
|
|
220
|
+
allDependencies.push(...componentRender.assets);
|
|
184
221
|
}
|
|
185
222
|
|
|
186
223
|
const triggers = this.collectResolvedTriggers(componentsToResolve);
|
|
187
224
|
if (triggers.length > 0) {
|
|
188
|
-
const globalAssets = await this.buildGlobalInjectorAssets(triggers,
|
|
225
|
+
const globalAssets = await this.buildGlobalInjectorAssets(triggers, adapter.name);
|
|
189
226
|
allDependencies.push(...globalAssets);
|
|
190
227
|
}
|
|
191
228
|
|
|
192
|
-
const eagerSsrLazyAssets = await this.buildEagerSsrLazyAssets(componentsToResolve,
|
|
229
|
+
const eagerSsrLazyAssets = await this.buildEagerSsrLazyAssets(componentsToResolve, adapter.name);
|
|
193
230
|
if (eagerSsrLazyAssets.length > 0) {
|
|
194
231
|
allDependencies.push(...eagerSsrLazyAssets);
|
|
195
232
|
}
|
|
196
233
|
|
|
197
234
|
const dedupedDependencies = dedupeProcessedAssets(allDependencies);
|
|
198
|
-
const pagePackage =
|
|
199
|
-
|
|
235
|
+
const pagePackage = createPagePackage(dedupedDependencies);
|
|
200
236
|
const pageProps = {
|
|
201
237
|
...props,
|
|
202
238
|
params: routeOptions.params || {},
|
|
203
239
|
query: routeOptions.query || {},
|
|
204
240
|
};
|
|
205
|
-
|
|
206
241
|
const cacheStrategy = (Page as EcoPageComponent<any>).cache;
|
|
207
242
|
const defaultCacheStrategy = this.appConfig.cache?.defaultStrategy ?? 'static';
|
|
208
243
|
const effectiveCacheStrategy = cacheStrategy ?? defaultCacheStrategy;
|
|
@@ -229,7 +264,7 @@ export class RenderPreparationService {
|
|
|
229
264
|
locals,
|
|
230
265
|
pageLocals,
|
|
231
266
|
cacheStrategy,
|
|
232
|
-
|
|
267
|
+
ownershipPlan,
|
|
233
268
|
};
|
|
234
269
|
|
|
235
270
|
return {
|
|
@@ -239,15 +274,107 @@ export class RenderPreparationService {
|
|
|
239
274
|
}
|
|
240
275
|
|
|
241
276
|
/**
|
|
242
|
-
*
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
277
|
+
* Captures one route render body as HTML while preserving a replayable body value.
|
|
278
|
+
*/
|
|
279
|
+
async captureHtmlRender(render: () => Promise<RouteRendererBody>): Promise<CapturedHtmlRenderResult> {
|
|
280
|
+
const renderedBody = await render();
|
|
281
|
+
const capturedRender = await this.captureRenderedBody(renderedBody);
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
body: capturedRender.body,
|
|
285
|
+
html: capturedRender.html,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Executes the full route-render flow and returns the final body plus cache strategy.
|
|
291
|
+
*/
|
|
292
|
+
async execute<C = unknown>(
|
|
293
|
+
options: RouteRendererOptions,
|
|
294
|
+
adapter: RouteRenderOrchestratorAdapter<C>,
|
|
295
|
+
): Promise<RouteRenderResult> {
|
|
296
|
+
const renderOptions = await this.prepareRenderOptions(options, adapter);
|
|
297
|
+
return this.executePrepared(renderOptions, adapter);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Executes the route-render finalization path for already prepared render options.
|
|
250
302
|
*/
|
|
303
|
+
async executePrepared<C = unknown>(
|
|
304
|
+
renderOptions: IntegrationRendererRenderOptions<C>,
|
|
305
|
+
adapter: RouteRenderOrchestratorAdapter<C>,
|
|
306
|
+
): Promise<RouteRenderResult> {
|
|
307
|
+
const renderExecution = await this.captureHtmlRender(async () => adapter.renderRouteBody(renderOptions));
|
|
308
|
+
const unresolvedArtifactInspection = inspectUnresolvedMarkerArtifactHtml(renderExecution.html);
|
|
309
|
+
const htmlFinalization = adapter.getRouteHtmlFinalization(renderOptions);
|
|
310
|
+
const hasUnresolvedMarkerHtml = unresolvedArtifactInspection.hasUnresolvedMarkerArtifacts;
|
|
311
|
+
|
|
312
|
+
if (hasUnresolvedMarkerHtml) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
'[ecopages] Route render returned unresolved eco-marker artifact HTML. Full-route unresolved-marker fallback has been removed; resolve mixed foreign children inside renderComponentWithForeignChildren().',
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const canReuseCapturedBody = !hasUnresolvedMarkerHtml && htmlFinalization.finalizeHtml === undefined;
|
|
319
|
+
|
|
320
|
+
if (canReuseCapturedBody) {
|
|
321
|
+
const body = await adapter.transformRouteResponse(
|
|
322
|
+
new Response(renderExecution.body as BodyInit, {
|
|
323
|
+
headers: {
|
|
324
|
+
'Content-Type': 'text/html',
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
body,
|
|
331
|
+
cacheStrategy: renderOptions.cacheStrategy,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const finalization = htmlFinalization.finalizeHtml
|
|
336
|
+
? htmlFinalization.finalizeHtml(unresolvedArtifactInspection.normalizedHtml)
|
|
337
|
+
: unresolvedArtifactInspection.normalizedHtml;
|
|
338
|
+
|
|
339
|
+
const body = await adapter.transformRouteResponse(
|
|
340
|
+
new Response(finalization, {
|
|
341
|
+
headers: {
|
|
342
|
+
'Content-Type': 'text/html',
|
|
343
|
+
},
|
|
344
|
+
}),
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
body,
|
|
349
|
+
cacheStrategy: renderOptions.cacheStrategy,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private async captureRenderedBody(body: RouteRendererBody): Promise<{ body: RouteRendererBody; html: string }> {
|
|
354
|
+
const response = new Response(body as BodyInit);
|
|
355
|
+
|
|
356
|
+
if (typeof body === 'string') {
|
|
357
|
+
return {
|
|
358
|
+
body,
|
|
359
|
+
html: await response.text(),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!response.body) {
|
|
364
|
+
return {
|
|
365
|
+
body,
|
|
366
|
+
html: await response.text(),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const [capturedBody, replayBody] = response.body.tee();
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
body: replayBody,
|
|
374
|
+
html: await new Response(capturedBody).text(),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
251
378
|
private collectResolvedTriggers(
|
|
252
379
|
components: (EcoComponent | Partial<EcoComponent>)[],
|
|
253
380
|
seen = new Set<object>(),
|
|
@@ -275,13 +402,6 @@ export class RenderPreparationService {
|
|
|
275
402
|
return triggers;
|
|
276
403
|
}
|
|
277
404
|
|
|
278
|
-
/**
|
|
279
|
-
* Collects global integration dependencies used by nested components belonging
|
|
280
|
-
* to integrations other than the current renderer.
|
|
281
|
-
*
|
|
282
|
-
* @param components Root component set.
|
|
283
|
-
* @returns Processed integration dependencies contributed by nested integrations.
|
|
284
|
-
*/
|
|
285
405
|
private collectUsedIntegrationDependencies(
|
|
286
406
|
components: (EcoComponent | Partial<EcoComponent>)[],
|
|
287
407
|
currentIntegrationName: string,
|
|
@@ -307,13 +427,6 @@ export class RenderPreparationService {
|
|
|
307
427
|
return dependencies;
|
|
308
428
|
}
|
|
309
429
|
|
|
310
|
-
/**
|
|
311
|
-
* Discovers integration names referenced by the component dependency graph.
|
|
312
|
-
*
|
|
313
|
-
* @param components Root component set.
|
|
314
|
-
* @param seen Internal visited set for shared graphs.
|
|
315
|
-
* @returns Set of integration names found in the graph.
|
|
316
|
-
*/
|
|
317
430
|
private collectIntegrationNames(
|
|
318
431
|
components: (EcoComponent | Partial<EcoComponent>)[],
|
|
319
432
|
seen = new Set<object>(),
|
|
@@ -348,38 +461,6 @@ export class RenderPreparationService {
|
|
|
348
461
|
return integrationNames;
|
|
349
462
|
}
|
|
350
463
|
|
|
351
|
-
/**
|
|
352
|
-
* Renders the page root through the component-level render contract so any
|
|
353
|
-
* integration-specific assets and root attributes are available before the main
|
|
354
|
-
* document render.
|
|
355
|
-
*
|
|
356
|
-
* @param input Page root render inputs.
|
|
357
|
-
* @returns Structured component render result.
|
|
358
|
-
*/
|
|
359
|
-
private async renderPageRoot(input: {
|
|
360
|
-
Page: EcoComponent;
|
|
361
|
-
props: Record<string, unknown>;
|
|
362
|
-
routeOptions: RouteRendererOptions;
|
|
363
|
-
callbacks: RenderPreparationCallbacks;
|
|
364
|
-
}): Promise<{ componentRender: ComponentRenderResult }> {
|
|
365
|
-
return {
|
|
366
|
-
componentRender: await input.callbacks.renderPageComponent({
|
|
367
|
-
component: input.Page,
|
|
368
|
-
props: {
|
|
369
|
-
...input.props,
|
|
370
|
-
params: input.routeOptions.params || {},
|
|
371
|
-
query: input.routeOptions.query || {},
|
|
372
|
-
},
|
|
373
|
-
}),
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Builds the runtime assets needed to bootstrap global lazy trigger execution.
|
|
379
|
-
*
|
|
380
|
-
* @param triggers Fully resolved lazy trigger definitions.
|
|
381
|
-
* @returns Processed assets that should be merged into the final dependency set.
|
|
382
|
-
*/
|
|
383
464
|
private async buildGlobalInjectorAssets(
|
|
384
465
|
triggers: ResolvedLazyTrigger[],
|
|
385
466
|
currentIntegrationName: string,
|
|
@@ -403,7 +484,10 @@ export class RenderPreparationService {
|
|
|
403
484
|
bundle: true,
|
|
404
485
|
});
|
|
405
486
|
|
|
406
|
-
return this.assetProcessingService.processDependencies(
|
|
487
|
+
return this.assetProcessingService.processDependencies(
|
|
488
|
+
[mapScript, bootstrapInlineScript],
|
|
489
|
+
currentIntegrationName,
|
|
490
|
+
);
|
|
407
491
|
}
|
|
408
492
|
|
|
409
493
|
private async buildEagerSsrLazyAssets(
|
|
@@ -81,8 +81,14 @@ type CollectComponentDependenciesOptions = {
|
|
|
81
81
|
export function collectComponentDependencies(
|
|
82
82
|
options: CollectComponentDependenciesOptions,
|
|
83
83
|
): CollectedComponentDependencies {
|
|
84
|
-
const {
|
|
85
|
-
|
|
84
|
+
const {
|
|
85
|
+
components,
|
|
86
|
+
integrationName,
|
|
87
|
+
resolveLazyScripts,
|
|
88
|
+
createEcopagesJsxLazyEntryName,
|
|
89
|
+
isEcopagesJsxIntegration,
|
|
90
|
+
errors,
|
|
91
|
+
} = options;
|
|
86
92
|
const dependencies: AssetDefinition[] = [];
|
|
87
93
|
const lazyScriptsByConfig = new Map<NonNullable<EcoComponent['config']>, Map<string, LazyGroup>>();
|
|
88
94
|
const lazyDependencyKeys = new Set<string>();
|
|
@@ -193,4 +199,4 @@ export function collectComponentDependencies(
|
|
|
193
199
|
dependencies,
|
|
194
200
|
lazyScriptsByConfig,
|
|
195
201
|
};
|
|
196
|
-
}
|
|
202
|
+
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
EcoComponentScriptEntry,
|
|
3
|
-
EcoComponentStylesheetEntry,
|
|
4
|
-
} from '../../types/public-types.ts';
|
|
1
|
+
import type { EcoComponentScriptEntry, EcoComponentStylesheetEntry } from '../../types/public-types.ts';
|
|
5
2
|
import type { AssetDefinition } from '../../services/assets/asset-processing-service/index.ts';
|
|
6
3
|
import { AssetFactory } from '../../services/assets/asset-processing-service/index.ts';
|
|
7
4
|
|
|
@@ -153,4 +150,4 @@ export function collectDeclaredAssetEntries(options: CollectDeclaredAssetEntries
|
|
|
153
150
|
}),
|
|
154
151
|
);
|
|
155
152
|
}
|
|
156
|
-
}
|
|
153
|
+
}
|