@ecopages/core 0.2.0-beta.38 → 0.2.0-beta.39
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 +1 -0
- package/package.json +27 -3
- package/src/adapters/bun/server-adapter.js +1 -0
- package/src/adapters/node/server-adapter.js +1 -0
- package/src/adapters/shared/http/explicit-static-render-preparation.js +1 -1
- package/src/adapters/shared/http/explicit-static-route-matcher.js +1 -1
- package/src/adapters/shared/http/fs-server-response-matcher.js +15 -7
- package/src/adapters/shared/runtime/collect-dev-prewarm-plan.d.ts +11 -0
- package/src/adapters/shared/runtime/collect-dev-prewarm-plan.js +29 -0
- package/src/adapters/shared/runtime/dev-static-route-prewarm.d.ts +16 -0
- package/src/adapters/shared/runtime/dev-static-route-prewarm.js +66 -0
- package/src/adapters/shared/runtime/render-context.js +1 -1
- package/src/adapters/shared/runtime/server-adapter.d.ts +7 -1
- package/src/adapters/shared/runtime/server-adapter.js +64 -5
- package/src/build/README.md +1 -1
- package/src/build/browser/browser-runtime-plugin.js +2 -5
- package/src/build/cache/production-build-cache.d.ts +3 -2
- package/src/build/cache/production-build-cache.js +2 -2
- package/src/build/contracts/content-virtual-modules.d.ts +12 -0
- package/src/build/contracts/content-virtual-modules.js +29 -0
- package/src/build/rolldown/rolldown-adapter-helpers.js +1 -1
- package/src/build/runtime/build-request-policy.d.ts +3 -4
- package/src/build/runtime/build-request-policy.js +4 -4
- package/src/cache/index.d.ts +2 -1
- package/src/cache/index.js +2 -1
- package/src/cache/module-parse-cache.d.ts +5 -0
- package/src/cache/module-parse-cache.js +27 -3
- package/src/cache/module-transform-profiler.d.ts +8 -0
- package/src/cache/module-transform-profiler.js +8 -0
- package/src/client/view-transitions.d.ts +17 -1
- package/src/client/view-transitions.js +45 -1
- package/src/client/view-transitions.test.browser.d.ts +1 -0
- package/src/client/view-transitions.test.browser.js +56 -0
- package/src/config/config-builder.d.ts +16 -9
- package/src/config/config-builder.js +24 -14
- package/src/dev/transform-server/dev-transform-vendor-registry.d.ts +1 -0
- package/src/dev/transform-server/dev-transform-vendor-registry.js +15 -2
- package/src/diagnostics/request-pipeline-metrics.d.ts +38 -0
- package/src/diagnostics/request-pipeline-metrics.js +121 -0
- package/src/eco/component-identity.d.ts +13 -0
- package/src/eco/component-identity.js +18 -0
- package/src/eco/eco-declared-component.d.ts +3 -2
- package/src/eco/eco-declared-component.js +6 -4
- package/src/eco/eco.browser.js +2 -2
- package/src/eco/eco.js +7 -6
- package/src/eco/eco.types.d.ts +6 -5
- package/src/errors/http-error.d.ts +8 -0
- package/src/errors/http-error.js +16 -0
- package/src/errors/undeclared-component-dependency-error.d.ts +1 -1
- package/src/errors/undeclared-component-dependency-error.js +2 -2
- package/src/index.browser.d.ts +1 -0
- package/src/index.browser.js +1 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/plugins/README.md +2 -1
- package/src/plugins/eco-component-meta-plugin.d.ts +6 -102
- package/src/plugins/eco-component-meta-plugin.js +110 -391
- package/src/plugins/processor.d.ts +10 -0
- package/src/plugins/processor.js +9 -0
- package/src/route-renderer/README.md +10 -8
- package/src/route-renderer/orchestration/document-shell/document-shell-render.service.js +1 -1
- package/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.js +1 -1
- package/src/route-renderer/orchestration/integration-renderer.d.ts +0 -8
- package/src/route-renderer/orchestration/integration-renderer.js +6 -19
- package/src/route-renderer/orchestration/ownership-graph/component-graph-collectors.js +5 -4
- package/src/route-renderer/orchestration/ownership-graph/component-graph.js +6 -4
- package/src/route-renderer/orchestration/ownership-graph/ownership-validation.service.d.ts +0 -1
- package/src/route-renderer/orchestration/ownership-graph/ownership-validation.service.js +10 -19
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.d.ts +1 -0
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js +12 -0
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.d.ts +9 -0
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.js +7 -0
- package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +65 -33
- package/src/route-renderer/page-loading/component-dependency-collection.js +3 -2
- package/src/route-renderer/page-loading/ecopages-virtual-imports.d.ts +0 -5
- package/src/route-renderer/page-loading/ecopages-virtual-imports.js +4 -10
- package/src/route-renderer/page-loading/file-scoped-dependency-components.d.ts +2 -2
- package/src/route-renderer/page-loading/file-scoped-dependency-components.js +12 -11
- package/src/route-renderer/page-loading/lazy-trigger-planning.js +2 -1
- package/src/route-renderer/page-loading/page-module-loader.d.ts +1 -1
- package/src/route-renderer/page-loading/page-module-loader.js +8 -3
- package/src/router/README.md +8 -0
- package/src/services/README.md +5 -0
- package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +1 -0
- package/src/services/assets/asset-processing-service/asset-processing.service.js +22 -5
- package/src/services/cache/cache.types.d.ts +4 -2
- package/src/services/cache/html-page-cache-dependency-index.d.ts +34 -0
- package/src/services/cache/html-page-cache-dependency-index.js +88 -0
- package/src/services/cache/index.d.ts +2 -0
- package/src/services/cache/index.js +2 -0
- package/src/services/cache/page-cache-service.d.ts +31 -1
- package/src/services/cache/page-cache-service.js +79 -3
- package/src/services/cache/page-request-cache-coordinator.service.js +4 -0
- package/src/services/module-loading/README.md +9 -6
- package/src/services/module-loading/app-server-module-transpiler.service.js +0 -3
- package/src/services/module-loading/collection-server-module-build.service.d.ts +19 -0
- package/src/services/module-loading/collection-server-module-build.service.js +83 -0
- package/src/services/module-loading/page-module-import.service.d.ts +5 -11
- package/src/services/module-loading/page-module-import.service.js +56 -73
- package/src/services/module-loading/route-module-build-cache.store.js +6 -6
- package/src/services/module-loading/route-module-build-manifest.d.ts +8 -5
- package/src/services/module-loading/route-module-build-manifest.js +20 -32
- package/src/services/module-loading/server-module-transpiler.service.d.ts +0 -2
- package/src/services/module-loading/server-module-transpiler.service.js +0 -3
- package/src/static-site-generator/static-site-generator.js +2 -2
- package/src/types/internal-types.d.ts +4 -0
- package/src/types/public-types.d.ts +18 -18
- package/src/watchers/project-watcher.js +14 -0
|
@@ -366,14 +366,14 @@ export class IntegrationRenderer {
|
|
|
366
366
|
? input.children.outerHTML
|
|
367
367
|
: undefined;
|
|
368
368
|
if (input.children !== undefined && serializedChildren === undefined) {
|
|
369
|
-
const componentFile = input.component.config?.
|
|
369
|
+
const componentFile = input.component.config?.identity?.file ?? 'unknown component';
|
|
370
370
|
const childTag = Object.prototype.toString.call(input.children);
|
|
371
371
|
throw new TypeError(`[ecopages] ${this.name} renderer expected serialized children for ${componentFile}, received ${childTag}.`);
|
|
372
372
|
}
|
|
373
373
|
const props = serializedChildren === undefined ? input.props : { ...input.props, children: serializedChildren };
|
|
374
374
|
const content = await component(props);
|
|
375
375
|
if (typeof content !== 'string') {
|
|
376
|
-
const componentFile = input.component.config?.
|
|
376
|
+
const componentFile = input.component.config?.identity?.file ?? 'unknown component';
|
|
377
377
|
const contentTag = Object.prototype.toString.call(content);
|
|
378
378
|
throw new TypeError(`[ecopages] ${this.name} renderer expected a string render result for ${componentFile}, received ${contentTag}.`);
|
|
379
379
|
}
|
|
@@ -453,33 +453,19 @@ export class IntegrationRenderer {
|
|
|
453
453
|
invariant(false, `Error importing HtmlTemplate: ${error}`);
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
|
-
usesIntegrationPageImporter(_file) {
|
|
457
|
-
return false;
|
|
458
|
-
}
|
|
459
|
-
async importIntegrationPageFile(_file, _options) {
|
|
460
|
-
invariant(false, 'Integration page importer must be implemented when enabled');
|
|
461
|
-
}
|
|
462
456
|
normalizeImportedPageFile(_file, pageModule) {
|
|
463
457
|
return pageModule;
|
|
464
458
|
}
|
|
465
459
|
/**
|
|
466
460
|
* Imports the page file from the specified path.
|
|
467
|
-
* It uses dynamic import to load the file and returns the imported module.
|
|
468
461
|
*
|
|
469
462
|
* @param file - The file path to import.
|
|
470
463
|
* @returns The imported module.
|
|
471
464
|
*/
|
|
472
465
|
async importPageFile(file, options) {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
bypassCache,
|
|
477
|
-
cacheScope: options?.cacheScope,
|
|
478
|
-
})
|
|
479
|
-
: await this.pageModuleLoaderService.importPageFile(file, {
|
|
480
|
-
bypassCache,
|
|
481
|
-
cacheScope: options?.cacheScope,
|
|
482
|
-
});
|
|
466
|
+
const pageModule = await this.pageModuleLoaderService.importPageFile(file, {
|
|
467
|
+
bypassCache: options?.bypassCache,
|
|
468
|
+
});
|
|
483
469
|
return this.normalizeImportedPageFile(file, pageModule);
|
|
484
470
|
}
|
|
485
471
|
/**
|
|
@@ -546,6 +532,7 @@ export class IntegrationRenderer {
|
|
|
546
532
|
async resolveRouteRenderInputs(routeOptions) {
|
|
547
533
|
const resolvedPageModule = await this.pageModuleLoaderService.resolvePageModule({
|
|
548
534
|
file: routeOptions.file,
|
|
535
|
+
pageModule: routeOptions.pageModule,
|
|
549
536
|
importPageFileFn: (targetFile) => this.importPageFile(targetFile),
|
|
550
537
|
});
|
|
551
538
|
const { Page, integrationSpecificProps, module: pageModule } = resolvedPageModule;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { AssetFactory } from '../../../services/assets/asset-processing-service/index.js';
|
|
3
3
|
import { walkComponentGraph } from './component-graph.js';
|
|
4
|
+
import { getComponentIdentity } from '../../../eco/component-identity.js';
|
|
4
5
|
function toGraphRoots(components) {
|
|
5
6
|
return components.filter(Boolean).map((component) => ({ component }));
|
|
6
7
|
}
|
|
@@ -10,7 +11,7 @@ export function collectIntegrationNamesFromGraph(components, currentIntegrationN
|
|
|
10
11
|
roots: toGraphRoots(components),
|
|
11
12
|
currentIntegrationName,
|
|
12
13
|
onComponent: ({ component }) => {
|
|
13
|
-
const integrationName = component
|
|
14
|
+
const integrationName = getComponentIdentity(component)?.integration ?? component.config?.integration;
|
|
14
15
|
if (integrationName) {
|
|
15
16
|
integrationNames.add(integrationName);
|
|
16
17
|
}
|
|
@@ -41,7 +42,7 @@ export function hasForeignChildDescendantsInGraph(component, currentIntegrationN
|
|
|
41
42
|
if (foundForeign) {
|
|
42
43
|
return false;
|
|
43
44
|
}
|
|
44
|
-
const integrationName = currentComponent
|
|
45
|
+
const integrationName = getComponentIdentity(currentComponent)?.integration ?? currentComponent.config?.integration;
|
|
45
46
|
if (integrationName && integrationName !== currentIntegrationName) {
|
|
46
47
|
foundForeign = true;
|
|
47
48
|
return false;
|
|
@@ -63,8 +64,8 @@ export function collectEagerSsrLazyScriptDefinitions(components) {
|
|
|
63
64
|
currentIntegrationName: '',
|
|
64
65
|
visitLayout: true,
|
|
65
66
|
onConfig: (config) => {
|
|
66
|
-
const componentFile = config?.
|
|
67
|
-
if (!componentFile) {
|
|
67
|
+
const componentFile = getComponentIdentity(config)?.file;
|
|
68
|
+
if (!componentFile || !config) {
|
|
68
69
|
return;
|
|
69
70
|
}
|
|
70
71
|
const componentDir = path.dirname(componentFile);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getComponentIdentity } from '../../../eco/component-identity.js';
|
|
1
2
|
export function walkComponentGraph(input) {
|
|
2
3
|
const seenComponents = new Set();
|
|
3
4
|
const seenConfigs = new Set();
|
|
@@ -10,7 +11,8 @@ export function walkComponentGraph(input) {
|
|
|
10
11
|
return;
|
|
11
12
|
}
|
|
12
13
|
seenComponents.add(ecoComponent);
|
|
13
|
-
const
|
|
14
|
+
const identity = getComponentIdentity(ecoComponent);
|
|
15
|
+
const integrationName = ecoComponent.config?.integration ?? identity?.integration ?? parentIntegrationName;
|
|
14
16
|
if (input.onConfig && ecoComponent.config && !seenConfigs.has(ecoComponent.config)) {
|
|
15
17
|
seenConfigs.add(ecoComponent.config);
|
|
16
18
|
input.onConfig(ecoComponent.config);
|
|
@@ -62,10 +64,10 @@ export function walkComponentGraph(input) {
|
|
|
62
64
|
export function mapComponentGraph(input) {
|
|
63
65
|
let nextSyntheticId = 0;
|
|
64
66
|
const mapComponent = (component, source, parentIntegrationName, lineage) => {
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
+
const identity = getComponentIdentity(component);
|
|
68
|
+
const integrationName = component.config?.integration ?? identity?.integration ?? parentIntegrationName;
|
|
67
69
|
const isForeignToParent = integrationName !== parentIntegrationName;
|
|
68
|
-
const componentId =
|
|
70
|
+
const componentId = identity?.id ?? identity?.file ?? `${source}:${(nextSyntheticId += 1)}`;
|
|
69
71
|
const nextLineage = new Set(lineage);
|
|
70
72
|
nextLineage.add(component);
|
|
71
73
|
const children = (component.config?.dependencies?.components ?? []).flatMap((child) => {
|
|
@@ -24,7 +24,6 @@ export declare class OwnershipValidationService {
|
|
|
24
24
|
* Validates foreign ownership edges reachable from the supplied route roots.
|
|
25
25
|
*/
|
|
26
26
|
validate(input: OwnershipValidationInput): OwnershipValidationError[];
|
|
27
|
-
private assertDeclaredComponentDependencies;
|
|
28
27
|
private isRegisteredIntegration;
|
|
29
28
|
}
|
|
30
29
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assertEcoDeclaredComponent } from '../../../eco/eco-declared-component.js';
|
|
2
|
-
import {
|
|
2
|
+
import { getComponentIdentity } from '../../../eco/component-identity.js';
|
|
3
|
+
import { mapComponentGraph } from './component-graph.js';
|
|
3
4
|
/**
|
|
4
5
|
* Validates foreign ownership as soon as the route root graph is loaded.
|
|
5
6
|
*
|
|
@@ -19,12 +20,17 @@ export class OwnershipValidationService {
|
|
|
19
20
|
* Validates foreign ownership edges reachable from the supplied route roots.
|
|
20
21
|
*/
|
|
21
22
|
validate(input) {
|
|
22
|
-
this.assertDeclaredComponentDependencies(input);
|
|
23
23
|
return mapComponentGraph({
|
|
24
24
|
roots: input.roots,
|
|
25
25
|
currentIntegrationName: input.currentIntegrationName,
|
|
26
26
|
mapNode: ({ component, integrationName, componentId, isForeignToParent }, children) => {
|
|
27
|
-
const
|
|
27
|
+
const parentFile = getComponentIdentity(component)?.file;
|
|
28
|
+
for (const child of component.config?.dependencies?.components ?? []) {
|
|
29
|
+
if (child) {
|
|
30
|
+
assertEcoDeclaredComponent(child, { parentComponentFile: parentFile });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const componentMeta = getComponentIdentity(component);
|
|
28
34
|
const errors = children.flat();
|
|
29
35
|
if (!isForeignToParent) {
|
|
30
36
|
return errors;
|
|
@@ -35,7 +41,7 @@ export class OwnershipValidationService {
|
|
|
35
41
|
if (!componentMeta) {
|
|
36
42
|
errors.push({
|
|
37
43
|
code: 'MISSING_COMPONENT_METADATA',
|
|
38
|
-
message: `[ecopages] Foreign child "${componentId}" must provide stable
|
|
44
|
+
message: `[ecopages] Foreign child "${componentId}" must provide stable component identity so ownership diagnostics stay actionable. Declared dependencies must include all possible foreign children.`,
|
|
39
45
|
componentId,
|
|
40
46
|
integrationName,
|
|
41
47
|
});
|
|
@@ -53,21 +59,6 @@ export class OwnershipValidationService {
|
|
|
53
59
|
},
|
|
54
60
|
}).flat();
|
|
55
61
|
}
|
|
56
|
-
assertDeclaredComponentDependencies(input) {
|
|
57
|
-
walkComponentGraph({
|
|
58
|
-
roots: input.roots,
|
|
59
|
-
currentIntegrationName: input.currentIntegrationName,
|
|
60
|
-
onComponent: ({ component }) => {
|
|
61
|
-
const parentFile = component.config?.__eco?.file;
|
|
62
|
-
for (const child of component.config?.dependencies?.components ?? []) {
|
|
63
|
-
if (!child) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
assertEcoDeclaredComponent(child, { parentComponentFile: parentFile });
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
62
|
isRegisteredIntegration(integrationName, currentIntegrationName) {
|
|
72
63
|
if (integrationName === currentIntegrationName) {
|
|
73
64
|
return true;
|
|
@@ -78,6 +78,7 @@ export declare class SessionPageBrowserGraphCache {
|
|
|
78
78
|
*/
|
|
79
79
|
getBuildCount(): number;
|
|
80
80
|
getGraphByRoute(integrationName: string, routeFile: string, policy: GraphPolicy, dependencyInstanceKey?: string): PageBrowserGraphResult | undefined;
|
|
81
|
+
getDependencyPathsByRoute(integrationName: string, routeFile: string, policy: GraphPolicy, dependencyInstanceKey?: string): ReadonlySet<string> | undefined;
|
|
81
82
|
getAffectedGraphIdentities(filePath: string): AffectedGraphIdentity[];
|
|
82
83
|
resolveGraph(key: GraphKey, provisionalDependencyPaths: ReadonlySet<string>, build: () => Promise<GraphBuildResult | undefined>): Promise<PageBrowserGraphResult | undefined>;
|
|
83
84
|
resolveGroupedGraph(scope: GroupedGraphScope, build: () => Promise<GroupedGraphBuildOutcome | undefined>): Promise<Map<string, ProcessedAsset[]>>;
|
|
@@ -139,6 +139,18 @@ export class SessionPageBrowserGraphCache {
|
|
|
139
139
|
}
|
|
140
140
|
return undefined;
|
|
141
141
|
}
|
|
142
|
+
getDependencyPathsByRoute(integrationName, routeFile, policy, dependencyInstanceKey = '') {
|
|
143
|
+
const normalizedRoute = normalizeDependencyPath(routeFile);
|
|
144
|
+
for (const record of this.records.values()) {
|
|
145
|
+
if (record.key.integrationName === integrationName &&
|
|
146
|
+
record.key.policy === policy &&
|
|
147
|
+
normalizeDependencyPath(record.key.routeFile) === normalizedRoute &&
|
|
148
|
+
record.key.dependencyInstanceKey === dependencyInstanceKey) {
|
|
149
|
+
return record.dependencyPaths;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
142
154
|
getAffectedGraphIdentities(filePath) {
|
|
143
155
|
const identities = [];
|
|
144
156
|
for (const serializedKey of this.dependencyIndex.getAffectedKeys(filePath)) {
|
|
@@ -24,6 +24,15 @@ export declare class PageBrowserGraphService {
|
|
|
24
24
|
private readonly assetProcessingService;
|
|
25
25
|
constructor(appConfig: EcoPagesAppConfig, assetProcessingService: AssetProcessingService);
|
|
26
26
|
resolvePageBrowserGraph(input: PageBrowserGraphResolveInput): Promise<PageBrowserGraphResult | undefined>;
|
|
27
|
+
/**
|
|
28
|
+
* Returns dependency paths recorded for one resolved Page Browser Graph.
|
|
29
|
+
*/
|
|
30
|
+
getDependencyPathsForRoute(input: {
|
|
31
|
+
integrationName: string;
|
|
32
|
+
routeFile: string;
|
|
33
|
+
dependencyInstanceKey?: string;
|
|
34
|
+
policy?: GraphPolicy;
|
|
35
|
+
}): ReadonlySet<string> | undefined;
|
|
27
36
|
/**
|
|
28
37
|
* Resolves or builds one Page Browser Graph under the given policy.
|
|
29
38
|
*/
|
|
@@ -23,6 +23,13 @@ export class PageBrowserGraphService {
|
|
|
23
23
|
async resolvePageBrowserGraph(input) {
|
|
24
24
|
return await this.getOrBuild(input);
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns dependency paths recorded for one resolved Page Browser Graph.
|
|
28
|
+
*/
|
|
29
|
+
getDependencyPathsForRoute(input) {
|
|
30
|
+
const policy = input.policy ?? this.resolveGraphPolicy();
|
|
31
|
+
return getAppPageBrowserGraphSession(this.appConfig).getDependencyPathsByRoute(input.integrationName, input.routeFile, policy, input.dependencyInstanceKey ?? '');
|
|
32
|
+
}
|
|
26
33
|
/**
|
|
27
34
|
* Resolves or builds one Page Browser Graph under the given policy.
|
|
28
35
|
*/
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { collectHtmlCacheSourceDependencyPaths } from '../../../services/cache/html-page-cache-dependency-index.js';
|
|
2
|
+
import { getComponentIdentity } from '../../../eco/component-identity.js';
|
|
1
3
|
import { inspectUnresolvedMarkerArtifactHtml } from './marker-artifact.utils.js';
|
|
2
4
|
import { OwnershipValidationService, throwIfOwnershipInvalid, } from '../ownership-graph/ownership-validation.service.js';
|
|
3
5
|
import { buildEagerSsrLazyAssetsFromGraph, collectResolvedLazyTriggersFromGraph, collectUsedIntegrationDependenciesFromGraph, } from '../ownership-graph/component-graph-collectors.js';
|
|
@@ -6,6 +8,7 @@ import { mergePageBrowserGraphContributions } from '../page-browser-graph/page-b
|
|
|
6
8
|
import { PageBrowserGraphService } from '../page-browser-graph/page-browser-graph.service.js';
|
|
7
9
|
import { createPageDependencyInstanceKey } from '../page-browser-graph/route-instance-key.js';
|
|
8
10
|
import { buildPreparedRenderOptions } from './route-prepared-options.builder.js';
|
|
11
|
+
import { measureRouteRenderPhase } from '../../../diagnostics/request-pipeline-metrics.js';
|
|
9
12
|
/**
|
|
10
13
|
* Owns one route render from normalized module loading through final HTML output.
|
|
11
14
|
*
|
|
@@ -35,7 +38,7 @@ export class RouteRenderOrchestrator {
|
|
|
35
38
|
* HTML transformation.
|
|
36
39
|
*/
|
|
37
40
|
async prepareRenderOptions(routeOptions, adapter) {
|
|
38
|
-
const resolvedInputs = await adapter.resolveRouteRenderInputs(routeOptions);
|
|
41
|
+
const resolvedInputs = await measureRouteRenderPhase('resolve-route-inputs', () => adapter.resolveRouteRenderInputs(routeOptions));
|
|
39
42
|
const finalProps = {
|
|
40
43
|
...resolvedInputs.props,
|
|
41
44
|
...(routeOptions.props ?? {}),
|
|
@@ -45,15 +48,17 @@ export class RouteRenderOrchestrator {
|
|
|
45
48
|
props: finalProps,
|
|
46
49
|
};
|
|
47
50
|
const { Page, HtmlTemplate, Layouts } = finalResolvedInputs;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
await measureRouteRenderPhase('ownership-validation', async () => {
|
|
52
|
+
const validationErrors = this.ownershipValidationService.validate({
|
|
53
|
+
currentIntegrationName: adapter.name,
|
|
54
|
+
roots: [
|
|
55
|
+
{ component: HtmlTemplate, source: 'html-template' },
|
|
56
|
+
...Layouts.map((layout) => ({ component: layout, source: 'layout' })),
|
|
57
|
+
{ component: Page, source: 'page' },
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
throwIfOwnershipInvalid(validationErrors);
|
|
55
61
|
});
|
|
56
|
-
throwIfOwnershipInvalid(validationErrors);
|
|
57
62
|
const componentsToResolve = [HtmlTemplate, ...Layouts, Page];
|
|
58
63
|
const dependencyInstanceKey = createPageDependencyInstanceKey({
|
|
59
64
|
params: routeOptions.params,
|
|
@@ -68,17 +73,20 @@ export class RouteRenderOrchestrator {
|
|
|
68
73
|
dependencyInstanceKey,
|
|
69
74
|
};
|
|
70
75
|
const resolvedPageDependencies = await adapter.resolvePageDependencies(graphContext);
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
const { resolvedDependencies } = await measureRouteRenderPhase('resolve-dependencies', () => adapter.resolveRouteDependencies({
|
|
77
|
+
components: componentsToResolve,
|
|
78
|
+
}));
|
|
79
|
+
const pageBrowserGraph = await measureRouteRenderPhase('page-browser-graph', () => this.pageBrowserGraphService.resolvePageBrowserGraph({
|
|
80
|
+
routeFile: routeOptions.file,
|
|
81
|
+
dependencyInstanceKey,
|
|
82
|
+
integrationName: adapter.name,
|
|
83
|
+
collectContribution: async () => mergePageBrowserGraphContributions(await adapter.collectPageBrowserGraphContribution(graphContext), resolvedPageDependencies?.contribution),
|
|
84
|
+
}));
|
|
85
|
+
const graphDependencyPaths = this.pageBrowserGraphService.getDependencyPathsForRoute({
|
|
86
|
+
integrationName: adapter.name,
|
|
87
|
+
routeFile: routeOptions.file,
|
|
88
|
+
dependencyInstanceKey,
|
|
89
|
+
});
|
|
82
90
|
const resolvedPageDependencyComponents = resolvedPageDependencies?.components ?? [];
|
|
83
91
|
const allDependencies = [
|
|
84
92
|
...resolvedDependencies,
|
|
@@ -92,15 +100,24 @@ export class RouteRenderOrchestrator {
|
|
|
92
100
|
buildEagerSsrLazyAssetsFromGraph(this.assetProcessingService, componentsToResolve, adapter.name),
|
|
93
101
|
]);
|
|
94
102
|
allDependencies.push(...globalAssets, ...eagerSsrLazyAssets);
|
|
95
|
-
|
|
96
|
-
routeOptions,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
allDependencies,
|
|
101
|
-
pageBrowserGraph,
|
|
102
|
-
appConfig: this.appConfig,
|
|
103
|
+
const sourceDependencyPaths = collectHtmlCacheSourceDependencyPaths({
|
|
104
|
+
routeFile: routeOptions.file,
|
|
105
|
+
processedAssets: allDependencies,
|
|
106
|
+
graphDependencyPaths,
|
|
107
|
+
additionalSourcePaths: collectRenderShellSourcePaths(this.appConfig, componentsToResolve),
|
|
103
108
|
});
|
|
109
|
+
return {
|
|
110
|
+
...buildPreparedRenderOptions({
|
|
111
|
+
routeOptions,
|
|
112
|
+
resolvedInputs: finalResolvedInputs,
|
|
113
|
+
resolvedPageDependencyComponents,
|
|
114
|
+
resolvedDependencies,
|
|
115
|
+
allDependencies,
|
|
116
|
+
pageBrowserGraph,
|
|
117
|
+
appConfig: this.appConfig,
|
|
118
|
+
}),
|
|
119
|
+
sourceDependencyPaths,
|
|
120
|
+
};
|
|
104
121
|
}
|
|
105
122
|
async resolveDeclaredPageBrowserGraph(input) {
|
|
106
123
|
return await this.pageBrowserGraphService.resolvePageBrowserGraph(input);
|
|
@@ -127,7 +144,7 @@ export class RouteRenderOrchestrator {
|
|
|
127
144
|
* Executes the route-render finalization path for already prepared render options.
|
|
128
145
|
*/
|
|
129
146
|
async executePrepared(renderOptions, adapter) {
|
|
130
|
-
const renderExecution = await this.captureHtmlRender(async () => adapter.renderRouteBody(renderOptions));
|
|
147
|
+
const renderExecution = await measureRouteRenderPhase('render-body', () => this.captureHtmlRender(async () => adapter.renderRouteBody(renderOptions)));
|
|
131
148
|
const unresolvedArtifactInspection = inspectUnresolvedMarkerArtifactHtml(renderExecution.html);
|
|
132
149
|
const htmlFinalization = adapter.getRouteHtmlFinalization(renderOptions);
|
|
133
150
|
const hasUnresolvedMarkerHtml = unresolvedArtifactInspection.hasUnresolvedMarkerArtifacts;
|
|
@@ -137,27 +154,29 @@ export class RouteRenderOrchestrator {
|
|
|
137
154
|
const canReuseCapturedBody = !hasUnresolvedMarkerHtml && htmlFinalization.finalizeHtml === undefined;
|
|
138
155
|
if (canReuseCapturedBody) {
|
|
139
156
|
const responseBody = typeof renderExecution.body === 'string' ? renderExecution.body : renderExecution.html;
|
|
140
|
-
const body = await adapter.transformRouteResponse(new Response(responseBody, {
|
|
157
|
+
const body = await measureRouteRenderPhase('transform-response', () => adapter.transformRouteResponse(new Response(responseBody, {
|
|
141
158
|
headers: {
|
|
142
159
|
'Content-Type': 'text/html',
|
|
143
160
|
},
|
|
144
|
-
}), htmlFinalization.htmlContributions, renderOptions.pagePackage);
|
|
161
|
+
}), htmlFinalization.htmlContributions, renderOptions.pagePackage));
|
|
145
162
|
return {
|
|
146
163
|
body,
|
|
147
164
|
cacheStrategy: renderOptions.cacheStrategy,
|
|
165
|
+
sourceDependencyPaths: renderOptions.sourceDependencyPaths,
|
|
148
166
|
};
|
|
149
167
|
}
|
|
150
168
|
const finalization = htmlFinalization.finalizeHtml
|
|
151
169
|
? htmlFinalization.finalizeHtml(unresolvedArtifactInspection.normalizedHtml)
|
|
152
170
|
: unresolvedArtifactInspection.normalizedHtml;
|
|
153
|
-
const body = await adapter.transformRouteResponse(new Response(finalization, {
|
|
171
|
+
const body = await measureRouteRenderPhase('transform-response', () => adapter.transformRouteResponse(new Response(finalization, {
|
|
154
172
|
headers: {
|
|
155
173
|
'Content-Type': 'text/html',
|
|
156
174
|
},
|
|
157
|
-
}), htmlFinalization.htmlContributions, renderOptions.pagePackage);
|
|
175
|
+
}), htmlFinalization.htmlContributions, renderOptions.pagePackage));
|
|
158
176
|
return {
|
|
159
177
|
body,
|
|
160
178
|
cacheStrategy: renderOptions.cacheStrategy,
|
|
179
|
+
sourceDependencyPaths: renderOptions.sourceDependencyPaths,
|
|
161
180
|
};
|
|
162
181
|
}
|
|
163
182
|
async captureRenderedBody(body) {
|
|
@@ -181,3 +200,16 @@ export class RouteRenderOrchestrator {
|
|
|
181
200
|
};
|
|
182
201
|
}
|
|
183
202
|
}
|
|
203
|
+
function collectRenderShellSourcePaths(appConfig, components) {
|
|
204
|
+
const sourcePaths = new Set();
|
|
205
|
+
if (appConfig.absolutePaths?.htmlTemplatePath) {
|
|
206
|
+
sourcePaths.add(appConfig.absolutePaths.htmlTemplatePath);
|
|
207
|
+
}
|
|
208
|
+
for (const component of components) {
|
|
209
|
+
const componentFile = getComponentIdentity(component)?.file;
|
|
210
|
+
if (componentFile) {
|
|
211
|
+
sourcePaths.add(componentFile);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return [...sourcePaths];
|
|
215
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { assertEcoDeclaredComponent } from '../../eco/eco-declared-component.js';
|
|
3
|
+
import { getComponentIdentity } from '../../eco/component-identity.js';
|
|
3
4
|
import { AssetFactory } from '../../services/assets/asset-processing-service/index.js';
|
|
4
5
|
import { extractEcopagesVirtualImports } from './ecopages-virtual-imports.js';
|
|
5
6
|
import { collectDeclaredAssetEntries } from './declared-asset-collection.js';
|
|
@@ -43,7 +44,7 @@ export function collectComponentDependencies(options) {
|
|
|
43
44
|
for (const component of components) {
|
|
44
45
|
if (!component)
|
|
45
46
|
continue;
|
|
46
|
-
const componentFile = component
|
|
47
|
+
const componentFile = getComponentIdentity(component)?.file;
|
|
47
48
|
if (!componentFile)
|
|
48
49
|
continue;
|
|
49
50
|
const stylesheetDependencyKeys = new Set();
|
|
@@ -55,7 +56,7 @@ export function collectComponentDependencies(options) {
|
|
|
55
56
|
const collect = (config) => {
|
|
56
57
|
if (!config)
|
|
57
58
|
return;
|
|
58
|
-
const file = config
|
|
59
|
+
const file = getComponentIdentity(config)?.file;
|
|
59
60
|
if (!file)
|
|
60
61
|
return;
|
|
61
62
|
const dir = path.dirname(file);
|
|
@@ -2,11 +2,6 @@ export type EcopagesVirtualImport = {
|
|
|
2
2
|
from: string;
|
|
3
3
|
imports: string[] | undefined;
|
|
4
4
|
};
|
|
5
|
-
/**
|
|
6
|
-
* @remarks
|
|
7
|
-
* Content server modules static-import every MDX entry. They must never become
|
|
8
|
-
* browser module-script dependencies discovered from page or component sources.
|
|
9
|
-
*/
|
|
10
5
|
export declare function isBrowserEcopagesVirtualImport(specifier: string): boolean;
|
|
11
6
|
/**
|
|
12
7
|
* Extracts runtime `ecopages:` virtual-module imports from a component source file.
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const CONTENT_SERVER_VIRTUAL_MODULE_PATTERN = /^ecopages:content\/[a-z][a-z0-9-]+\/server$/;
|
|
5
|
-
/**
|
|
6
|
-
* @remarks
|
|
7
|
-
* Content server modules static-import every MDX entry. They must never become
|
|
8
|
-
* browser module-script dependencies discovered from page or component sources.
|
|
9
|
-
*/
|
|
2
|
+
import { isContentServerVirtualModule } from '../../build/contracts/content-virtual-modules.js';
|
|
3
|
+
import { parseModuleSource } from '../../cache/module-parse-cache.js';
|
|
10
4
|
export function isBrowserEcopagesVirtualImport(specifier) {
|
|
11
|
-
return specifier.startsWith('ecopages:') && !
|
|
5
|
+
return specifier.startsWith('ecopages:') && !isContentServerVirtualModule(specifier);
|
|
12
6
|
}
|
|
13
7
|
/**
|
|
14
8
|
* Extracts runtime `ecopages:` virtual-module imports from a component source file.
|
|
@@ -26,7 +20,7 @@ export function extractEcopagesVirtualImports(file) {
|
|
|
26
20
|
}
|
|
27
21
|
let result;
|
|
28
22
|
try {
|
|
29
|
-
result =
|
|
23
|
+
result = parseModuleSource(file, source);
|
|
30
24
|
}
|
|
31
25
|
catch {
|
|
32
26
|
return [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EcoComponent, EcoComponentConfig, EcoComponentDependencies } from '../../types/public-types.js';
|
|
2
2
|
import type { PageDependenciesResult } from '../../eco/eco.types.js';
|
|
3
3
|
/**
|
|
4
|
-
* Attaches file-backed
|
|
4
|
+
* Attaches canonical file-backed identity to one component config.
|
|
5
5
|
*/
|
|
6
6
|
export declare function attachEcoFileMetadataToConfig(config: EcoComponentConfig, ownerFile: string, integrationName: string): EcoComponentConfig;
|
|
7
7
|
/**
|
|
@@ -26,7 +26,7 @@ export type CollectComponentConfigFilePathsOptions = {
|
|
|
26
26
|
includeLayouts?: boolean;
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
|
-
* Walks component configs and collects every resolved
|
|
29
|
+
* Walks component configs and collects every resolved identity file path.
|
|
30
30
|
*/
|
|
31
31
|
export declare function collectComponentConfigFilePaths(components: ReadonlyArray<EcoComponent | Partial<EcoComponent> | undefined>, options?: CollectComponentConfigFilePathsOptions): Set<string>;
|
|
32
32
|
/**
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { rapidhash } from '../../utils/hash.js';
|
|
3
|
+
import { bindComponentIdentity, getComponentIdentity } from '../../eco/component-identity.js';
|
|
3
4
|
/**
|
|
4
|
-
* Attaches file-backed
|
|
5
|
+
* Attaches canonical file-backed identity to one component config.
|
|
5
6
|
*/
|
|
6
7
|
export function attachEcoFileMetadataToConfig(config, ownerFile, integrationName) {
|
|
7
|
-
return {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
integration: config.__eco?.integration ?? integrationName,
|
|
13
|
-
},
|
|
14
|
-
};
|
|
8
|
+
return bindComponentIdentity({
|
|
9
|
+
id: getComponentIdentity(config)?.id ?? rapidhash(ownerFile).toString(36),
|
|
10
|
+
file: ownerFile,
|
|
11
|
+
integration: getComponentIdentity(config)?.integration ?? integrationName,
|
|
12
|
+
}, config);
|
|
15
13
|
}
|
|
16
14
|
/**
|
|
17
15
|
* Builds a synthetic component config rooted at one file so direct dependency
|
|
@@ -45,7 +43,7 @@ export function collectFileScopedDependencyComponents(options) {
|
|
|
45
43
|
return components;
|
|
46
44
|
}
|
|
47
45
|
/**
|
|
48
|
-
* Walks component configs and collects every resolved
|
|
46
|
+
* Walks component configs and collects every resolved identity file path.
|
|
49
47
|
*/
|
|
50
48
|
export function collectComponentConfigFilePaths(components, options) {
|
|
51
49
|
const files = new Set();
|
|
@@ -54,10 +52,13 @@ export function collectComponentConfigFilePaths(components, options) {
|
|
|
54
52
|
files.add(path.resolve(additionalPath));
|
|
55
53
|
}
|
|
56
54
|
const visit = (config) => {
|
|
57
|
-
const file = config?.
|
|
55
|
+
const file = getComponentIdentity(config)?.file;
|
|
58
56
|
if (!file) {
|
|
59
57
|
return;
|
|
60
58
|
}
|
|
59
|
+
if (!config) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
61
62
|
const resolved = path.resolve(file);
|
|
62
63
|
if (visited.has(resolved)) {
|
|
63
64
|
return;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getComponentIdentity } from '../../eco/component-identity.js';
|
|
1
2
|
import { rapidhash } from '../../utils/hash.js';
|
|
2
3
|
/**
|
|
3
4
|
* Creates a stable grouping key for a lazy trigger declaration.
|
|
@@ -24,7 +25,7 @@ export function getLazyTriggerKey(lazy) {
|
|
|
24
25
|
export function buildResolvedLazyTriggers(config, groups) {
|
|
25
26
|
if (groups.length === 0)
|
|
26
27
|
return [];
|
|
27
|
-
const componentFile = config
|
|
28
|
+
const componentFile = getComponentIdentity(config)?.file ?? '';
|
|
28
29
|
const sortedUrls = groups
|
|
29
30
|
.flatMap((group) => group.scripts)
|
|
30
31
|
.sort()
|
|
@@ -29,7 +29,6 @@ export declare class PageModuleLoaderService {
|
|
|
29
29
|
*/
|
|
30
30
|
importPageFile(file: string, options?: {
|
|
31
31
|
bypassCache?: boolean;
|
|
32
|
-
cacheScope?: string;
|
|
33
32
|
}): Promise<EcoPageFile>;
|
|
34
33
|
/**
|
|
35
34
|
* Executes the page's static-props hook with Ecopages runtime context.
|
|
@@ -62,6 +61,7 @@ export declare class PageModuleLoaderService {
|
|
|
62
61
|
*/
|
|
63
62
|
resolvePageModule(options: {
|
|
64
63
|
file: string;
|
|
64
|
+
pageModule?: EcoPageFile;
|
|
65
65
|
importPageFileFn?: (file: string) => Promise<EcoPageFile>;
|
|
66
66
|
}): Promise<{
|
|
67
67
|
module: EcoPageFile;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { invariant } from '../../utils/invariant.js';
|
|
2
2
|
import { getAppModuleLoader } from '../../services/module-loading/app-server-module-transpiler.service.js';
|
|
3
3
|
import { resolveInternalExecutionDir } from '../../utils/resolve-work-dir.js';
|
|
4
|
+
import { HttpError } from '../../errors/http-error.js';
|
|
4
5
|
/**
|
|
5
6
|
* Loads route page modules and normalizes their data hooks for rendering.
|
|
6
7
|
*
|
|
@@ -39,7 +40,6 @@ export class PageModuleLoaderService {
|
|
|
39
40
|
rootDir: this.appConfig.rootDir,
|
|
40
41
|
outdir: `${resolveInternalExecutionDir(this.appConfig)}/.server-modules`,
|
|
41
42
|
bypassCache: options?.bypassCache,
|
|
42
|
-
cacheScope: options?.cacheScope,
|
|
43
43
|
transpileErrorMessage: (details) => `Error transpiling page file: ${details}`,
|
|
44
44
|
noOutputMessage: (targetFilePath) => `No transpiled output generated for page: ${targetFilePath}`,
|
|
45
45
|
});
|
|
@@ -65,7 +65,11 @@ export class PageModuleLoaderService {
|
|
|
65
65
|
})
|
|
66
66
|
.then((data) => data)
|
|
67
67
|
.catch((err) => {
|
|
68
|
-
|
|
68
|
+
if (HttpError.isHttpError(err) || err instanceof Response) {
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
72
|
+
throw new Error(`Error fetching static props: ${message}`, { cause: err });
|
|
69
73
|
})
|
|
70
74
|
: {
|
|
71
75
|
props: {},
|
|
@@ -98,7 +102,8 @@ export class PageModuleLoaderService {
|
|
|
98
102
|
* When both component static methods and module exports exist, component statics win.
|
|
99
103
|
*/
|
|
100
104
|
async resolvePageModule(options) {
|
|
101
|
-
const module =
|
|
105
|
+
const module = options.pageModule ??
|
|
106
|
+
(await (options.importPageFileFn ?? ((file) => this.importPageFile(file)))(options.file));
|
|
102
107
|
const { default: Page, getStaticProps: moduleGetStaticProps, getMetadata: moduleGetMetadata, ...integrationSpecificProps } = module;
|
|
103
108
|
return {
|
|
104
109
|
module,
|