@ecopages/core 0.2.0-beta.36 → 0.2.0-beta.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -3
- package/src/dev-toolbar/README.md +9 -9
- package/src/eco/eco.browser.js +9 -2
- package/src/eco/eco.js +8 -2
- package/src/eco/eco.types.d.ts +29 -2
- package/src/hmr/README.md +5 -5
- package/src/route-renderer/README.md +2 -1
- package/src/route-renderer/orchestration/integration-renderer.d.ts +19 -3
- package/src/route-renderer/orchestration/integration-renderer.js +89 -8
- package/src/route-renderer/orchestration/page-browser-graph/grouped-graph-build-plan.d.ts +15 -0
- package/src/route-renderer/orchestration/page-browser-graph/grouped-graph-build-plan.js +9 -0
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.merge.d.ts +5 -0
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.merge.js +30 -0
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.d.ts +22 -6
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js +145 -68
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.d.ts +9 -2
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph.service.js +56 -39
- package/src/route-renderer/orchestration/page-browser-graph/route-instance-key.d.ts +29 -0
- package/src/route-renderer/orchestration/page-browser-graph/route-instance-key.js +54 -0
- package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.d.ts +3 -0
- package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.js +3 -2
- package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.builder.d.ts +1 -0
- package/src/route-renderer/orchestration/route-pipeline/route-prepared-options.builder.js +3 -5
- package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.d.ts +14 -4
- package/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.js +31 -4
- package/src/route-renderer/page-loading/file-scoped-dependency-components.d.ts +42 -0
- package/src/route-renderer/page-loading/file-scoped-dependency-components.js +93 -0
- package/src/route-renderer/page-loading/page-module-loader.d.ts +1 -0
- package/src/route-renderer/page-loading/page-module-loader.js +1 -0
- package/src/route-renderer/page-loading/resolved-page-dependencies.d.ts +12 -0
- package/src/route-renderer/page-loading/resolved-page-dependencies.js +38 -0
- package/src/static-site-generator/production-page-browser-graph-prebuild.d.ts +10 -6
- package/src/static-site-generator/production-page-browser-graph-prebuild.js +29 -10
- package/src/static-site-generator/static-site-generator.js +4 -1
- package/src/types/public-types.d.ts +9 -1
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.loader.d.ts +0 -9
- package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-contribution.loader.js +0 -11
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds a stable cache key for one concrete dependency resolver input.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* The representation deliberately retains scalar versus array values. Delimiter-based
|
|
6
|
+
* encodings would make otherwise distinct route inputs collide.
|
|
7
|
+
*/
|
|
8
|
+
export function createPageDependencyInstanceKey(input) {
|
|
9
|
+
const params = canonicalizeInput(input.params);
|
|
10
|
+
const query = canonicalizeInput(input.query);
|
|
11
|
+
if (params.length === 0 && query.length === 0) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
return JSON.stringify({ params, query });
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Combines a route file with an optional dependency-instance key for grouped graph maps.
|
|
18
|
+
*/
|
|
19
|
+
export function createRouteGraphLookupKey(routeFile, dependencyInstanceKey = '') {
|
|
20
|
+
return JSON.stringify([routeFile, dependencyInstanceKey]);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Serializes the grouped graph cache scope for one integration and dependency instance.
|
|
24
|
+
*/
|
|
25
|
+
export function serializeGroupedGraphCacheKey(scope) {
|
|
26
|
+
return JSON.stringify(['grouped', scope.integrationName, scope.planKey]);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Reconstructs dependency resolver inputs from a serialized dependency-instance key.
|
|
30
|
+
*/
|
|
31
|
+
export function parsePageDependencyInstanceKey(dependencyInstanceKey) {
|
|
32
|
+
if (!dependencyInstanceKey) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
const parsed = JSON.parse(dependencyInstanceKey);
|
|
36
|
+
if (!isSerializedDependencyInstance(parsed)) {
|
|
37
|
+
throw new TypeError('Invalid page dependency instance key.');
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...(parsed.params.length > 0 ? { params: Object.fromEntries(parsed.params) } : {}),
|
|
41
|
+
...(parsed.query.length > 0 ? { query: Object.fromEntries(parsed.query) } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function canonicalizeInput(input) {
|
|
45
|
+
return Object.entries(input ?? {})
|
|
46
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
47
|
+
.map(([key, value]) => [key, Array.isArray(value) ? [...value] : value]);
|
|
48
|
+
}
|
|
49
|
+
function isSerializedDependencyInstance(value) {
|
|
50
|
+
if (!value || typeof value !== 'object' || !('params' in value) || !('query' in value)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return Array.isArray(value.params) && Array.isArray(value.query);
|
|
54
|
+
}
|
package/src/route-renderer/orchestration/route-pipeline/integration-route-render-adapter.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ProcessedAsset } from '../../../services/assets/asset-processing-s
|
|
|
2
2
|
import type { HtmlDocumentContribution } from '../../../services/html/html-transformer.service.js';
|
|
3
3
|
import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
|
|
4
4
|
import type { EcoComponent, EcoPageFile, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PagePackageResult, RouteRendererBody, RouteRendererOptions } from '../../../types/public-types.js';
|
|
5
|
+
import type { ResolvedPageDependencies } from '../../page-loading/resolved-page-dependencies.js';
|
|
5
6
|
import type { RouteHtmlFinalization, RouteRenderOrchestratorAdapter, RouteRenderOrchestratorResolvedInputs } from './route-render-orchestrator.js';
|
|
6
7
|
/**
|
|
7
8
|
* Host surface required to build the orchestrator adapter for one integration renderer.
|
|
@@ -19,6 +20,8 @@ export type IntegrationRouteRenderAdapterHost<C> = {
|
|
|
19
20
|
}>;
|
|
20
21
|
importPageFile(file: string): Promise<EcoPageFile>;
|
|
21
22
|
collectPageBrowserGraphContribution(context: PageBrowserGraphContributionContext): Promise<PageBrowserGraphContribution | undefined>;
|
|
23
|
+
resolvePageDependencies(context: PageBrowserGraphContributionContext): Promise<ResolvedPageDependencies | undefined>;
|
|
24
|
+
buildPageBrowserGraphContributionContext(routeFile: string, routeOptions?: Pick<RouteRendererOptions, 'params' | 'query'>): Promise<PageBrowserGraphContributionContext>;
|
|
22
25
|
renderRouteBody(renderOptions: IntegrationRendererRenderOptions<C>): Promise<RouteRendererBody>;
|
|
23
26
|
getDocumentAttributes(renderOptions: IntegrationRendererRenderOptions<C>): Record<string, string> | undefined;
|
|
24
27
|
getHtmlDocumentContributions(options: {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { loadPageBrowserGraphContribution } from '../page-browser-graph/page-browser-graph-contribution.loader.js';
|
|
2
1
|
import { buildRouteHtmlFinalization } from './route-html-finalization.service.js';
|
|
3
2
|
/**
|
|
4
3
|
* Builds the internal route-render adapter consumed by `RouteRenderOrchestrator`.
|
|
@@ -8,7 +7,9 @@ export function createIntegrationRouteRenderAdapter(host) {
|
|
|
8
7
|
name: host.name,
|
|
9
8
|
resolveRouteRenderInputs: (routeOptions) => host.resolveRouteRenderInputs(routeOptions),
|
|
10
9
|
resolveRouteDependencies: (input) => host.resolveRouteDependencies(input),
|
|
11
|
-
collectPageBrowserGraphContribution: (
|
|
10
|
+
collectPageBrowserGraphContribution: (context) => host.collectPageBrowserGraphContribution(context),
|
|
11
|
+
resolvePageDependencies: (context) => host.resolvePageDependencies(context),
|
|
12
|
+
buildPageBrowserGraphContributionContext: (routeFile, routeOptions) => host.buildPageBrowserGraphContributionContext(routeFile, routeOptions),
|
|
12
13
|
renderRouteBody: (renderOptions) => host.renderRouteBody(renderOptions),
|
|
13
14
|
getRouteHtmlFinalization: (renderOptions) => buildRouteHtmlFinalization({
|
|
14
15
|
appConfig: host.appConfig,
|
|
@@ -17,6 +17,7 @@ type PreparedRenderInputs = {
|
|
|
17
17
|
export declare function buildPreparedRenderOptions<C = unknown>(input: {
|
|
18
18
|
routeOptions: RouteRendererOptions;
|
|
19
19
|
resolvedInputs: PreparedRenderInputs;
|
|
20
|
+
resolvedPageDependencyComponents?: ReadonlyArray<EcoComponent | Partial<EcoComponent>>;
|
|
20
21
|
resolvedDependencies: ProcessedAsset[];
|
|
21
22
|
allDependencies: ProcessedAsset[];
|
|
22
23
|
pageBrowserGraph?: PageBrowserGraphResult;
|
|
@@ -5,14 +5,11 @@ import { createPageLocalsProxy } from './route-prepared-options.utils.js';
|
|
|
5
5
|
* Assembles the final integration render options after route prep dependencies resolve.
|
|
6
6
|
*/
|
|
7
7
|
export function buildPreparedRenderOptions(input) {
|
|
8
|
-
const { routeOptions, resolvedInputs, resolvedDependencies, allDependencies, pageBrowserGraph, appConfig } = input;
|
|
8
|
+
const { routeOptions, resolvedInputs, resolvedPageDependencyComponents, resolvedDependencies, allDependencies, pageBrowserGraph, appConfig, } = input;
|
|
9
9
|
const { Page, HtmlTemplate, Layouts, Layout, layoutEntries, props, metadata, integrationSpecificProps } = resolvedInputs;
|
|
10
10
|
const dedupedDependencies = dedupeProcessedAssets(allDependencies);
|
|
11
11
|
const pagePackage = createPagePackage(dedupedDependencies, { pageBrowserGraph });
|
|
12
|
-
const resolvedProps =
|
|
13
|
-
...props,
|
|
14
|
-
...(routeOptions.props ?? {}),
|
|
15
|
-
};
|
|
12
|
+
const resolvedProps = props;
|
|
16
13
|
const pageProps = {
|
|
17
14
|
...resolvedProps,
|
|
18
15
|
params: routeOptions.params || {},
|
|
@@ -28,6 +25,7 @@ export function buildPreparedRenderOptions(input) {
|
|
|
28
25
|
const locals = localsAvailable ? routeOptions.locals : undefined;
|
|
29
26
|
const preparedOptions = {
|
|
30
27
|
...routeOptions,
|
|
28
|
+
resolvedPageDependencyComponents,
|
|
31
29
|
resolvedDependencies,
|
|
32
30
|
pagePackage,
|
|
33
31
|
HtmlTemplate: HtmlTemplate,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
|
|
2
|
-
import type { EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult, EcoPageLayoutEntry } from '../../../types/public-types.js';
|
|
2
|
+
import type { EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult, EcoPageLayoutEntry } from '../../../types/public-types.js';
|
|
3
3
|
import type { AssetProcessingService, ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
|
|
4
4
|
import type { HtmlDocumentContribution } from '../../../services/html/html-transformer.service.js';
|
|
5
5
|
import { OwnershipValidationService } from '../ownership-graph/ownership-validation.service.js';
|
|
6
6
|
import { PageBrowserGraphService } from '../page-browser-graph/page-browser-graph.service.js';
|
|
7
|
+
import type { GroupedGraphBuildPlan } from '../page-browser-graph/grouped-graph-build-plan.js';
|
|
8
|
+
import type { ResolvedPageDependencies } from '../../page-loading/resolved-page-dependencies.js';
|
|
7
9
|
export type RouteRenderOrchestratorResolvedInputs = {
|
|
8
10
|
Page: EcoPageFile['default'] | EcoPageComponent<any>;
|
|
11
|
+
pageModule: EcoPageFile;
|
|
9
12
|
HtmlTemplate: EcoComponent<HtmlTemplateProps>;
|
|
10
13
|
Layouts: EcoComponent[];
|
|
11
14
|
Layout?: EcoComponent;
|
|
@@ -43,9 +46,14 @@ export interface RouteRenderOrchestratorAdapter<C> {
|
|
|
43
46
|
components: (EcoComponent | Partial<EcoComponent>)[];
|
|
44
47
|
}): Promise<RouteRenderOrchestratorResolvedDependencies>;
|
|
45
48
|
/**
|
|
46
|
-
* Collects declarative Page Browser Graph requirements for one route.
|
|
49
|
+
* Collects declarative Page Browser Graph requirements for one route instance.
|
|
47
50
|
*/
|
|
48
|
-
collectPageBrowserGraphContribution(
|
|
51
|
+
collectPageBrowserGraphContribution(context: PageBrowserGraphContributionContext): Promise<PageBrowserGraphContribution | undefined>;
|
|
52
|
+
resolvePageDependencies(context: PageBrowserGraphContributionContext): Promise<ResolvedPageDependencies | undefined>;
|
|
53
|
+
/**
|
|
54
|
+
* Builds the graph contribution context for one route file and optional params.
|
|
55
|
+
*/
|
|
56
|
+
buildPageBrowserGraphContributionContext(routeFile: string, routeOptions?: Pick<RouteRendererOptions, 'params' | 'query'>): Promise<PageBrowserGraphContributionContext>;
|
|
49
57
|
/**
|
|
50
58
|
* Executes the Integration-specific route render.
|
|
51
59
|
*/
|
|
@@ -97,8 +105,10 @@ export declare class RouteRenderOrchestrator {
|
|
|
97
105
|
prepareRenderOptions<C = unknown>(routeOptions: RouteRendererOptions, adapter: RouteRenderOrchestratorAdapter<C>): Promise<IntegrationRendererRenderOptions<C>>;
|
|
98
106
|
resolveDeclaredPageBrowserGraph(input: {
|
|
99
107
|
routeFile: string;
|
|
108
|
+
dependencyInstanceKey?: string;
|
|
100
109
|
integrationName: string;
|
|
101
|
-
collectContribution: (
|
|
110
|
+
collectContribution: () => Promise<PageBrowserGraphContribution | undefined>;
|
|
111
|
+
groupedBuildPlan?: GroupedGraphBuildPlan;
|
|
102
112
|
}): Promise<PageBrowserGraphResult | undefined>;
|
|
103
113
|
/**
|
|
104
114
|
* Captures one route render body as HTML while preserving a replayable body value.
|
|
@@ -2,7 +2,9 @@ import { inspectUnresolvedMarkerArtifactHtml } from './marker-artifact.utils.js'
|
|
|
2
2
|
import { OwnershipValidationService, throwIfOwnershipInvalid, } from '../ownership-graph/ownership-validation.service.js';
|
|
3
3
|
import { buildEagerSsrLazyAssetsFromGraph, collectResolvedLazyTriggersFromGraph, collectUsedIntegrationDependenciesFromGraph, } from '../ownership-graph/component-graph-collectors.js';
|
|
4
4
|
import { buildGlobalInjectorAssets } from '../page-browser-graph/global-injector-assets.service.js';
|
|
5
|
+
import { mergePageBrowserGraphContributions } from '../page-browser-graph/page-browser-graph-contribution.merge.js';
|
|
5
6
|
import { PageBrowserGraphService } from '../page-browser-graph/page-browser-graph.service.js';
|
|
7
|
+
import { createPageDependencyInstanceKey } from '../page-browser-graph/route-instance-key.js';
|
|
6
8
|
import { buildPreparedRenderOptions } from './route-prepared-options.builder.js';
|
|
7
9
|
/**
|
|
8
10
|
* Owns one route render from normalized module loading through final HTML output.
|
|
@@ -34,7 +36,15 @@ export class RouteRenderOrchestrator {
|
|
|
34
36
|
*/
|
|
35
37
|
async prepareRenderOptions(routeOptions, adapter) {
|
|
36
38
|
const resolvedInputs = await adapter.resolveRouteRenderInputs(routeOptions);
|
|
37
|
-
const
|
|
39
|
+
const finalProps = {
|
|
40
|
+
...resolvedInputs.props,
|
|
41
|
+
...(routeOptions.props ?? {}),
|
|
42
|
+
};
|
|
43
|
+
const finalResolvedInputs = {
|
|
44
|
+
...resolvedInputs,
|
|
45
|
+
props: finalProps,
|
|
46
|
+
};
|
|
47
|
+
const { Page, HtmlTemplate, Layouts } = finalResolvedInputs;
|
|
38
48
|
const validationErrors = this.ownershipValidationService.validate({
|
|
39
49
|
currentIntegrationName: adapter.name,
|
|
40
50
|
roots: [
|
|
@@ -45,16 +55,31 @@ export class RouteRenderOrchestrator {
|
|
|
45
55
|
});
|
|
46
56
|
throwIfOwnershipInvalid(validationErrors);
|
|
47
57
|
const componentsToResolve = [HtmlTemplate, ...Layouts, Page];
|
|
58
|
+
const dependencyInstanceKey = createPageDependencyInstanceKey({
|
|
59
|
+
params: routeOptions.params,
|
|
60
|
+
query: routeOptions.query,
|
|
61
|
+
});
|
|
62
|
+
const graphContext = {
|
|
63
|
+
file: routeOptions.file,
|
|
64
|
+
pageModule: finalResolvedInputs.pageModule,
|
|
65
|
+
props: finalProps,
|
|
66
|
+
params: routeOptions.params,
|
|
67
|
+
query: routeOptions.query,
|
|
68
|
+
dependencyInstanceKey,
|
|
69
|
+
};
|
|
70
|
+
const resolvedPageDependencies = await adapter.resolvePageDependencies(graphContext);
|
|
48
71
|
const [{ resolvedDependencies }, pageBrowserGraph] = await Promise.all([
|
|
49
72
|
adapter.resolveRouteDependencies({
|
|
50
73
|
components: componentsToResolve,
|
|
51
74
|
}),
|
|
52
75
|
this.pageBrowserGraphService.resolvePageBrowserGraph({
|
|
53
76
|
routeFile: routeOptions.file,
|
|
77
|
+
dependencyInstanceKey,
|
|
54
78
|
integrationName: adapter.name,
|
|
55
|
-
collectContribution: async (
|
|
79
|
+
collectContribution: async () => mergePageBrowserGraphContributions(await adapter.collectPageBrowserGraphContribution(graphContext), resolvedPageDependencies?.contribution),
|
|
56
80
|
}),
|
|
57
81
|
]);
|
|
82
|
+
const resolvedPageDependencyComponents = resolvedPageDependencies?.components ?? [];
|
|
58
83
|
const allDependencies = [
|
|
59
84
|
...resolvedDependencies,
|
|
60
85
|
...collectUsedIntegrationDependenciesFromGraph(this.appConfig, componentsToResolve, adapter.name),
|
|
@@ -69,7 +94,8 @@ export class RouteRenderOrchestrator {
|
|
|
69
94
|
allDependencies.push(...globalAssets, ...eagerSsrLazyAssets);
|
|
70
95
|
return buildPreparedRenderOptions({
|
|
71
96
|
routeOptions,
|
|
72
|
-
resolvedInputs,
|
|
97
|
+
resolvedInputs: finalResolvedInputs,
|
|
98
|
+
resolvedPageDependencyComponents,
|
|
73
99
|
resolvedDependencies,
|
|
74
100
|
allDependencies,
|
|
75
101
|
pageBrowserGraph,
|
|
@@ -110,7 +136,8 @@ export class RouteRenderOrchestrator {
|
|
|
110
136
|
}
|
|
111
137
|
const canReuseCapturedBody = !hasUnresolvedMarkerHtml && htmlFinalization.finalizeHtml === undefined;
|
|
112
138
|
if (canReuseCapturedBody) {
|
|
113
|
-
const
|
|
139
|
+
const responseBody = typeof renderExecution.body === 'string' ? renderExecution.body : renderExecution.html;
|
|
140
|
+
const body = await adapter.transformRouteResponse(new Response(responseBody, {
|
|
114
141
|
headers: {
|
|
115
142
|
'Content-Type': 'text/html',
|
|
116
143
|
},
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EcoComponent, EcoComponentConfig, EcoComponentDependencies } from '../../types/public-types.js';
|
|
2
|
+
import type { PageDependenciesResult } from '../../eco/eco.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Attaches file-backed `__eco` metadata to one component config.
|
|
5
|
+
*/
|
|
6
|
+
export declare function attachEcoFileMetadataToConfig(config: EcoComponentConfig, ownerFile: string, integrationName: string): EcoComponentConfig;
|
|
7
|
+
/**
|
|
8
|
+
* Builds a synthetic component config rooted at one file so direct dependency
|
|
9
|
+
* declarations can flow through the shared component dependency collector.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createFileScopedDependencyComponent(options: {
|
|
12
|
+
ownerFile: string;
|
|
13
|
+
integrationName: string;
|
|
14
|
+
dependencies: Pick<EcoComponentDependencies, 'scripts' | 'stylesheets' | 'modules'>;
|
|
15
|
+
}): Partial<EcoComponent> | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Expands one dependency bag into component roots understood by the shared collector.
|
|
18
|
+
*/
|
|
19
|
+
export declare function collectFileScopedDependencyComponents(options: {
|
|
20
|
+
ownerFile: string;
|
|
21
|
+
integrationName: string;
|
|
22
|
+
dependencies: EcoComponentDependencies;
|
|
23
|
+
}): Array<EcoComponent | Partial<EcoComponent>>;
|
|
24
|
+
export type CollectComponentConfigFilePathsOptions = {
|
|
25
|
+
additionalPaths?: ReadonlyArray<string>;
|
|
26
|
+
includeLayouts?: boolean;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Walks component configs and collects every resolved `config.__eco.file` path.
|
|
30
|
+
*/
|
|
31
|
+
export declare function collectComponentConfigFilePaths(components: ReadonlyArray<EcoComponent | Partial<EcoComponent> | undefined>, options?: CollectComponentConfigFilePathsOptions): Set<string>;
|
|
32
|
+
/**
|
|
33
|
+
* Collects source files that should invalidate a per-instance browser graph entry.
|
|
34
|
+
*/
|
|
35
|
+
export declare function collectDependencyWatchPaths(ownerFile: string, components: ReadonlyArray<EcoComponent | Partial<EcoComponent>>): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Separates optional owner metadata from a resolved page dependency bag.
|
|
38
|
+
*/
|
|
39
|
+
export declare function splitPageDependenciesResult(result: PageDependenciesResult): {
|
|
40
|
+
dependencies: EcoComponentDependencies;
|
|
41
|
+
ownerFile?: string;
|
|
42
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { rapidhash } from '../../utils/hash.js';
|
|
3
|
+
/**
|
|
4
|
+
* Attaches file-backed `__eco` metadata to one component config.
|
|
5
|
+
*/
|
|
6
|
+
export function attachEcoFileMetadataToConfig(config, ownerFile, integrationName) {
|
|
7
|
+
return {
|
|
8
|
+
...config,
|
|
9
|
+
__eco: {
|
|
10
|
+
id: config.__eco?.id ?? rapidhash(ownerFile).toString(36),
|
|
11
|
+
file: ownerFile,
|
|
12
|
+
integration: config.__eco?.integration ?? integrationName,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Builds a synthetic component config rooted at one file so direct dependency
|
|
18
|
+
* declarations can flow through the shared component dependency collector.
|
|
19
|
+
*/
|
|
20
|
+
export function createFileScopedDependencyComponent(options) {
|
|
21
|
+
const { scripts, stylesheets, modules } = options.dependencies;
|
|
22
|
+
if (!scripts?.length && !stylesheets?.length && !modules?.length) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
config: attachEcoFileMetadataToConfig({ dependencies: { scripts, stylesheets, modules } }, options.ownerFile, options.integrationName),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Expands one dependency bag into component roots understood by the shared collector.
|
|
31
|
+
*/
|
|
32
|
+
export function collectFileScopedDependencyComponents(options) {
|
|
33
|
+
const components = [];
|
|
34
|
+
if (options.dependencies.components?.length) {
|
|
35
|
+
components.push(...options.dependencies.components);
|
|
36
|
+
}
|
|
37
|
+
const ownerComponent = createFileScopedDependencyComponent({
|
|
38
|
+
ownerFile: options.ownerFile,
|
|
39
|
+
integrationName: options.integrationName,
|
|
40
|
+
dependencies: options.dependencies,
|
|
41
|
+
});
|
|
42
|
+
if (ownerComponent) {
|
|
43
|
+
components.push(ownerComponent);
|
|
44
|
+
}
|
|
45
|
+
return components;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Walks component configs and collects every resolved `config.__eco.file` path.
|
|
49
|
+
*/
|
|
50
|
+
export function collectComponentConfigFilePaths(components, options) {
|
|
51
|
+
const files = new Set();
|
|
52
|
+
const visited = new Set();
|
|
53
|
+
for (const additionalPath of options?.additionalPaths ?? []) {
|
|
54
|
+
files.add(path.resolve(additionalPath));
|
|
55
|
+
}
|
|
56
|
+
const visit = (config) => {
|
|
57
|
+
const file = config?.__eco?.file;
|
|
58
|
+
if (!file) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const resolved = path.resolve(file);
|
|
62
|
+
if (visited.has(resolved)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
visited.add(resolved);
|
|
66
|
+
files.add(resolved);
|
|
67
|
+
for (const dependency of config.dependencies?.components ?? []) {
|
|
68
|
+
visit(dependency?.config);
|
|
69
|
+
}
|
|
70
|
+
if (options?.includeLayouts) {
|
|
71
|
+
for (const layout of config.layouts ?? []) {
|
|
72
|
+
visit(layout?.config);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
for (const component of components) {
|
|
77
|
+
visit(component?.config);
|
|
78
|
+
}
|
|
79
|
+
return files;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Collects source files that should invalidate a per-instance browser graph entry.
|
|
83
|
+
*/
|
|
84
|
+
export function collectDependencyWatchPaths(ownerFile, components) {
|
|
85
|
+
return Array.from(collectComponentConfigFilePaths(components, { additionalPaths: [ownerFile] }));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Separates optional owner metadata from a resolved page dependency bag.
|
|
89
|
+
*/
|
|
90
|
+
export function splitPageDependenciesResult(result) {
|
|
91
|
+
const { ownerFile, ...dependencies } = result;
|
|
92
|
+
return { dependencies, ownerFile };
|
|
93
|
+
}
|
|
@@ -64,6 +64,7 @@ export declare class PageModuleLoaderService {
|
|
|
64
64
|
file: string;
|
|
65
65
|
importPageFileFn?: (file: string) => Promise<EcoPageFile>;
|
|
66
66
|
}): Promise<{
|
|
67
|
+
module: EcoPageFile;
|
|
67
68
|
Page: EcoPageFile['default'] | EcoPageComponent<any>;
|
|
68
69
|
getStaticProps?: GetStaticProps<Record<string, unknown>>;
|
|
69
70
|
getMetadata?: GetMetadata;
|
|
@@ -101,6 +101,7 @@ export class PageModuleLoaderService {
|
|
|
101
101
|
const module = await (options.importPageFileFn ?? ((file) => this.importPageFile(file)))(options.file);
|
|
102
102
|
const { default: Page, getStaticProps: moduleGetStaticProps, getMetadata: moduleGetMetadata, ...integrationSpecificProps } = module;
|
|
103
103
|
return {
|
|
104
|
+
module,
|
|
104
105
|
Page,
|
|
105
106
|
getStaticProps: Page.staticProps ?? moduleGetStaticProps,
|
|
106
107
|
getMetadata: Page.metadata ?? moduleGetMetadata,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EcoComponent, PageBrowserGraphContribution, PageBrowserGraphContributionContext } from '../../types/public-types.js';
|
|
2
|
+
import { collectFileScopedDependencyComponents } from './file-scoped-dependency-components.js';
|
|
3
|
+
export type ResolvedPageDependencies = {
|
|
4
|
+
ownerFile: string;
|
|
5
|
+
components: ReadonlyArray<EcoComponent | Partial<EcoComponent>>;
|
|
6
|
+
contribution?: PageBrowserGraphContribution;
|
|
7
|
+
};
|
|
8
|
+
export type ResolvePageDependenciesContribution = (dependencies: Parameters<typeof collectFileScopedDependencyComponents>[0]['dependencies'], ownerFile: string, components: ReadonlyArray<EcoComponent | Partial<EcoComponent>>) => Promise<PageBrowserGraphContribution | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Resolves `eco.page().dependencies()` once for one graph contribution context.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolvePageDependenciesFromContext(context: PageBrowserGraphContributionContext, integrationName: string, materializeContribution: ResolvePageDependenciesContribution): Promise<ResolvedPageDependencies | undefined>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { collectFileScopedDependencyComponents, splitPageDependenciesResult, } from './file-scoped-dependency-components.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves `eco.page().dependencies()` once for one graph contribution context.
|
|
4
|
+
*/
|
|
5
|
+
export async function resolvePageDependenciesFromContext(context, integrationName, materializeContribution) {
|
|
6
|
+
const pageComponent = context.pageModule.default;
|
|
7
|
+
const resolveDependencies = pageComponent.resolveDependencies;
|
|
8
|
+
if (!resolveDependencies) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const dependenciesResult = await resolveDependencies({
|
|
12
|
+
props: (context.props ?? {}),
|
|
13
|
+
params: context.params,
|
|
14
|
+
query: context.query,
|
|
15
|
+
});
|
|
16
|
+
if (!dependenciesResult) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const { dependencies, ownerFile } = splitPageDependenciesResult(dependenciesResult);
|
|
20
|
+
const resolvedOwnerFile = ownerFile ?? context.file;
|
|
21
|
+
const components = collectFileScopedDependencyComponents({
|
|
22
|
+
ownerFile: resolvedOwnerFile,
|
|
23
|
+
integrationName,
|
|
24
|
+
dependencies,
|
|
25
|
+
});
|
|
26
|
+
if (components.length === 0) {
|
|
27
|
+
return {
|
|
28
|
+
ownerFile: resolvedOwnerFile,
|
|
29
|
+
components,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const contribution = await materializeContribution(dependencies, resolvedOwnerFile, components);
|
|
33
|
+
return {
|
|
34
|
+
ownerFile: resolvedOwnerFile,
|
|
35
|
+
components,
|
|
36
|
+
contribution,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { EcoPagesAppConfig } from '../types/internal-types.js';
|
|
2
|
+
import type { PageParams } from '../types/public-types.js';
|
|
2
3
|
import type { PageRendererResolver } from '../route-renderer/route-renderer.js';
|
|
4
|
+
export type ProductionPageBrowserGraphRouteInstance = {
|
|
5
|
+
routeFile: string;
|
|
6
|
+
params?: PageParams;
|
|
7
|
+
};
|
|
3
8
|
/**
|
|
4
9
|
* Returns whether production static export should warm Page Browser Graphs before rendering.
|
|
5
10
|
*
|
|
@@ -17,12 +22,11 @@ export declare function shouldPrebuildProductionPageBrowserGraphs(): boolean;
|
|
|
17
22
|
*/
|
|
18
23
|
export declare function clearProductionPageBrowserGraphSession(appConfig: EcoPagesAppConfig): void;
|
|
19
24
|
/**
|
|
20
|
-
* Warms production Page Browser Graphs for the supplied static route
|
|
25
|
+
* Warms production Page Browser Graphs for the supplied static route instances.
|
|
21
26
|
*
|
|
22
27
|
* @remarks
|
|
23
|
-
* Deduplicates
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* `page-browser-graph-session`). Does not persist a disk manifest.
|
|
28
|
+
* Deduplicates route file + params pairs, then calls each integration renderer's
|
|
29
|
+
* `prebuildProductionPageBrowserGraph` sequentially (activates integration runtime,
|
|
30
|
+
* then resolves into `page-browser-graph-session`). Does not persist a disk manifest.
|
|
27
31
|
*/
|
|
28
|
-
export declare function prebuildProductionPageBrowserGraphs(
|
|
32
|
+
export declare function prebuildProductionPageBrowserGraphs(routeInstances: readonly ProductionPageBrowserGraphRouteInstance[], routeRendererFactory: PageRendererResolver): Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { getAppPageBrowserGraphSession } from '../route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js';
|
|
3
|
+
import { createPageDependencyInstanceKey } from '../route-renderer/orchestration/page-browser-graph/route-instance-key.js';
|
|
3
4
|
/**
|
|
4
5
|
* Returns whether production static export should warm Page Browser Graphs before rendering.
|
|
5
6
|
*
|
|
@@ -20,19 +21,37 @@ export function shouldPrebuildProductionPageBrowserGraphs() {
|
|
|
20
21
|
export function clearProductionPageBrowserGraphSession(appConfig) {
|
|
21
22
|
getAppPageBrowserGraphSession(appConfig).clearPolicyRecords('production');
|
|
22
23
|
}
|
|
24
|
+
function serializeProductionRouteInstance(instance) {
|
|
25
|
+
const routeFile = path.resolve(instance.routeFile);
|
|
26
|
+
const dependencyInstanceKey = createPageDependencyInstanceKey({ params: instance.params });
|
|
27
|
+
return JSON.stringify([routeFile, dependencyInstanceKey]);
|
|
28
|
+
}
|
|
23
29
|
/**
|
|
24
|
-
* Warms production Page Browser Graphs for the supplied static route
|
|
30
|
+
* Warms production Page Browser Graphs for the supplied static route instances.
|
|
25
31
|
*
|
|
26
32
|
* @remarks
|
|
27
|
-
* Deduplicates
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `page-browser-graph-session`). Does not persist a disk manifest.
|
|
33
|
+
* Deduplicates route file + params pairs, then calls each integration renderer's
|
|
34
|
+
* `prebuildProductionPageBrowserGraph` sequentially (activates integration runtime,
|
|
35
|
+
* then resolves into `page-browser-graph-session`). Does not persist a disk manifest.
|
|
31
36
|
*/
|
|
32
|
-
export async function prebuildProductionPageBrowserGraphs(
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
export async function prebuildProductionPageBrowserGraphs(routeInstances, routeRendererFactory) {
|
|
38
|
+
const uniqueRouteInstances = [
|
|
39
|
+
...new Map(routeInstances.map((instance) => [serializeProductionRouteInstance(instance), instance])).values(),
|
|
40
|
+
].sort((left, right) => serializeProductionRouteInstance(left).localeCompare(serializeProductionRouteInstance(right)));
|
|
41
|
+
const instancesByRenderer = new Map();
|
|
42
|
+
for (const routeInstance of uniqueRouteInstances) {
|
|
43
|
+
const renderer = routeRendererFactory.getPageRenderer(routeInstance.routeFile);
|
|
44
|
+
const instances = instancesByRenderer.get(renderer) ?? [];
|
|
45
|
+
instances.push(routeInstance);
|
|
46
|
+
instancesByRenderer.set(renderer, instances);
|
|
47
|
+
}
|
|
48
|
+
for (const [renderer, routeInstancesForRenderer] of instancesByRenderer) {
|
|
49
|
+
const groupedBuildPlan = await renderer.buildGroupedGraphBuildPlan(routeInstancesForRenderer);
|
|
50
|
+
for (const routeInstance of routeInstancesForRenderer) {
|
|
51
|
+
await renderer.prebuildProductionPageBrowserGraph(routeInstance.routeFile, {
|
|
52
|
+
params: routeInstance.params,
|
|
53
|
+
groupedBuildPlan,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
37
56
|
}
|
|
38
57
|
}
|
|
@@ -357,7 +357,10 @@ export class StaticSiteGenerator {
|
|
|
357
357
|
await this.invokeStaticExportHook('beforeStaticExport', staticExportContext);
|
|
358
358
|
try {
|
|
359
359
|
if (shouldPrebuildProductionPageBrowserGraphs() && routeRendererFactory) {
|
|
360
|
-
await prebuildProductionPageBrowserGraphs(routes.map((route) =>
|
|
360
|
+
await prebuildProductionPageBrowserGraphs(routes.map((route) => ({
|
|
361
|
+
routeFile: route.templateRoute.filePath,
|
|
362
|
+
params: route.params,
|
|
363
|
+
})), routeRendererFactory);
|
|
361
364
|
}
|
|
362
365
|
this.generateRobotsTxt();
|
|
363
366
|
await this.generateStaticPages({
|
|
@@ -10,7 +10,7 @@ import type { InteractionEventsString as ScriptsInjectorInteractionEventsString
|
|
|
10
10
|
import type { EntrypointDependencyGraph } from '../services/runtime-state/entrypoint-dependency-graph.service.js';
|
|
11
11
|
import type { DevTransformBundleContributor } from '../dev/transform-server/types.js';
|
|
12
12
|
export type { EcoPagesAppConfig } from './internal-types.js';
|
|
13
|
-
export type { EcoPageComponent } from '../eco/eco.types.js';
|
|
13
|
+
export type { EcoPageComponent, GetPageDependencies, GetPageDependenciesContext, PageDependenciesResult, } from '../eco/eco.types.js';
|
|
14
14
|
export type { ProcessedAsset } from '../services/assets/asset-processing-service/assets.types.js';
|
|
15
15
|
/**
|
|
16
16
|
* Runtime-agnostic incoming WebSocket frame.
|
|
@@ -881,6 +881,8 @@ export type IntegrationRendererRenderOptions<C = EcoPagesElement> = RouteRendere
|
|
|
881
881
|
Layout?: EcoPageLayoutComponent<any>;
|
|
882
882
|
layoutEntries?: EcoPageLayoutEntry[];
|
|
883
883
|
dependencies?: EcoComponentDependencies;
|
|
884
|
+
/** @internal Resolved page dependencies carried through the render pipeline. */
|
|
885
|
+
resolvedPageDependencyComponents?: ReadonlyArray<EcoComponent | Partial<EcoComponent>>;
|
|
884
886
|
resolvedDependencies: ProcessedAsset[];
|
|
885
887
|
pagePackage?: PagePackageResult;
|
|
886
888
|
pageProps?: Record<string, unknown>;
|
|
@@ -950,10 +952,16 @@ export interface PageBrowserGraphResult {
|
|
|
950
952
|
export type PageBrowserGraphContributionContext = {
|
|
951
953
|
file: string;
|
|
952
954
|
pageModule: EcoPageFile;
|
|
955
|
+
props?: Record<string, unknown>;
|
|
956
|
+
params?: PageParams;
|
|
957
|
+
query?: PageQuery;
|
|
958
|
+
dependencyInstanceKey?: string;
|
|
953
959
|
};
|
|
954
960
|
export interface PageBrowserGraphContribution {
|
|
955
961
|
dependencies?: AssetDefinition[];
|
|
956
962
|
assets?: ProcessedAsset[];
|
|
963
|
+
/** Source files that should invalidate this graph entry when they change. */
|
|
964
|
+
watchPaths?: string[];
|
|
957
965
|
}
|
|
958
966
|
export type OwnershipValidationErrorCode = 'UNKNOWN_INTEGRATION_OWNER' | 'MISSING_COMPONENT_METADATA';
|
|
959
967
|
export interface OwnershipValidationError {
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { EcoPageFile, PageBrowserGraphContribution, PageBrowserGraphContributionContext } from '../../../types/public-types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Loads a page module once and collects declarative browser-graph requirements.
|
|
4
|
-
*
|
|
5
|
-
* @remarks
|
|
6
|
-
* Shared by route orchestration prep and the integration route-render adapter so
|
|
7
|
-
* page imports are not duplicated when both paths need the same contribution.
|
|
8
|
-
*/
|
|
9
|
-
export declare function loadPageBrowserGraphContribution(routeFile: string, importPageFile: (file: string) => Promise<EcoPageFile>, collectContribution: (context: PageBrowserGraphContributionContext) => Promise<PageBrowserGraphContribution | undefined>): Promise<PageBrowserGraphContribution | undefined>;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Loads a page module once and collects declarative browser-graph requirements.
|
|
3
|
-
*
|
|
4
|
-
* @remarks
|
|
5
|
-
* Shared by route orchestration prep and the integration route-render adapter so
|
|
6
|
-
* page imports are not duplicated when both paths need the same contribution.
|
|
7
|
-
*/
|
|
8
|
-
export async function loadPageBrowserGraphContribution(routeFile, importPageFile, collectContribution) {
|
|
9
|
-
const pageModule = await importPageFile(routeFile);
|
|
10
|
-
return collectContribution({ file: routeFile, pageModule });
|
|
11
|
-
}
|