@ecopages/core 0.2.0-alpha.39 → 0.2.0-alpha.40
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 +2 -2
- package/src/adapters/bun/create-app.d.ts +8 -1
- package/src/adapters/bun/create-app.js +52 -65
- package/src/adapters/bun/hmr-manager.d.ts +19 -103
- package/src/adapters/bun/hmr-manager.js +26 -280
- package/src/adapters/bun/runtime-host.d.ts +52 -0
- package/src/adapters/bun/runtime-host.js +56 -0
- package/src/adapters/bun/server-adapter.d.ts +89 -28
- package/src/adapters/bun/server-adapter.js +113 -61
- package/src/adapters/bun/static-preview-host.d.ts +28 -0
- package/src/adapters/bun/static-preview-host.js +45 -0
- package/src/adapters/node/create-app.d.ts +9 -3
- package/src/adapters/node/create-app.js +24 -81
- package/src/adapters/node/http-request-bridge.d.ts +57 -0
- package/src/adapters/node/http-request-bridge.js +118 -0
- package/src/adapters/node/node-hmr-manager.d.ts +22 -91
- package/src/adapters/node/node-hmr-manager.js +26 -272
- package/src/adapters/node/runtime-host.d.ts +57 -0
- package/src/adapters/node/runtime-host.js +92 -0
- package/src/adapters/node/server-adapter-dependencies.d.ts +19 -0
- package/src/adapters/node/server-adapter-dependencies.js +18 -0
- package/src/adapters/node/server-adapter.d.ts +10 -37
- package/src/adapters/node/server-adapter.js +55 -125
- package/src/adapters/node/static-preview-host.d.ts +55 -0
- package/src/adapters/node/static-preview-host.js +68 -0
- package/src/adapters/shared/runtime-app-bootstrap.d.ts +26 -0
- package/src/adapters/shared/runtime-app-bootstrap.js +46 -0
- package/src/adapters/shared/runtime-host.d.ts +12 -0
- package/src/adapters/shared/runtime-host.js +0 -0
- package/src/adapters/shared/shared-hmr-manager.d.ts +59 -0
- package/src/adapters/shared/shared-hmr-manager.js +239 -0
- package/src/adapters/shared/static-preview-host.d.ts +10 -0
- package/src/adapters/shared/static-preview-host.js +0 -0
- package/src/build/build-adapter.js +12 -1
- package/src/build/esbuild-build-adapter.d.ts +1 -0
- package/src/build/esbuild-build-adapter.js +13 -0
- package/src/hmr/strategies/js-hmr-strategy.js +0 -4
- package/src/plugins/integration-plugin.d.ts +6 -1
- package/src/route-renderer/orchestration/integration-renderer.d.ts +32 -14
- package/src/route-renderer/orchestration/integration-renderer.js +80 -14
- package/src/route-renderer/orchestration/processed-asset-dedupe.d.ts +1 -0
- package/src/route-renderer/orchestration/processed-asset-dedupe.js +15 -11
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +22 -8
- package/src/route-renderer/orchestration/route-render-orchestrator.js +59 -10
- package/src/services/assets/asset-processing-service/page-package.d.ts +4 -1
- package/src/services/assets/asset-processing-service/page-package.js +11 -5
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +3 -1
- package/src/services/html/html-rewriter-provider.service.d.ts +3 -0
- package/src/services/html/html-transformer.service.d.ts +10 -1
- package/src/services/html/html-transformer.service.js +80 -9
- package/src/services/module-loading/page-module-import.service.js +2 -2
- package/src/types/public-types.d.ts +24 -7
- package/src/adapters/bun/server-lifecycle.d.ts +0 -63
- package/src/adapters/bun/server-lifecycle.js +0 -92
- package/src/adapters/shared/runtime-bootstrap.d.ts +0 -38
- package/src/adapters/shared/runtime-bootstrap.js +0 -43
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ForeignSubtreeExecutionService
|
|
16
16
|
} from "./foreign-subtree-execution.service.js";
|
|
17
17
|
import {} from "./queued-foreign-subtree-resolution.service.js";
|
|
18
|
+
import { buildProcessedAssetDedupeKey } from "./processed-asset-dedupe.js";
|
|
18
19
|
function isMarkupNodeLike(value) {
|
|
19
20
|
return typeof value === "object" && value !== null && "nodeType" in value && typeof value.nodeType === "number" && "outerHTML" in value && typeof value.outerHTML === "string";
|
|
20
21
|
}
|
|
@@ -179,6 +180,46 @@ class IntegrationRenderer {
|
|
|
179
180
|
this.htmlTransformer.setPagePackage(createPagePackage(resolvedDependencies));
|
|
180
181
|
return resolvedDependencies;
|
|
181
182
|
}
|
|
183
|
+
async resolvePageBrowserGraphForFile(filePath) {
|
|
184
|
+
return await this.routeRenderOrchestrator.resolveDeclaredPageBrowserGraph({
|
|
185
|
+
routeFile: filePath,
|
|
186
|
+
integrationName: this.name,
|
|
187
|
+
collectContribution: async () => {
|
|
188
|
+
const pageModule = await this.importPageFile(filePath);
|
|
189
|
+
return await this.collectPageBrowserGraphContribution({ file: filePath, pageModule });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
mergePageBrowserGraphIntoPagePackage(pageBrowserGraph) {
|
|
194
|
+
if (!pageBrowserGraph) {
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
197
|
+
const currentPagePackage = this.htmlTransformer.getPagePackage();
|
|
198
|
+
const mergedPageBrowserGraph = currentPagePackage?.pageBrowserGraph ? {
|
|
199
|
+
entryAssets: this.htmlTransformer.dedupeProcessedAssets([
|
|
200
|
+
...currentPagePackage.pageBrowserGraph.entryAssets,
|
|
201
|
+
...pageBrowserGraph.entryAssets
|
|
202
|
+
]),
|
|
203
|
+
chunkAssets: this.htmlTransformer.dedupeProcessedAssets([
|
|
204
|
+
...currentPagePackage.pageBrowserGraph.chunkAssets,
|
|
205
|
+
...pageBrowserGraph.chunkAssets
|
|
206
|
+
])
|
|
207
|
+
} : pageBrowserGraph;
|
|
208
|
+
const pageBrowserGraphAssetKeys = new Set(
|
|
209
|
+
[...mergedPageBrowserGraph.entryAssets, ...mergedPageBrowserGraph.chunkAssets].map(
|
|
210
|
+
(asset) => buildProcessedAssetDedupeKey(asset)
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
const baseAssets = currentPagePackage ? currentPagePackage.assets.filter(
|
|
214
|
+
(asset) => !pageBrowserGraphAssetKeys.has(buildProcessedAssetDedupeKey(asset))
|
|
215
|
+
) : this.htmlTransformer.getProcessedDependencies();
|
|
216
|
+
this.htmlTransformer.setPagePackage(
|
|
217
|
+
createPagePackage(this.htmlTransformer.dedupeProcessedAssets(baseAssets), {
|
|
218
|
+
pageBrowserGraph: mergedPageBrowserGraph
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
return mergedPageBrowserGraph;
|
|
222
|
+
}
|
|
182
223
|
/**
|
|
183
224
|
* Merges component-scoped assets into the active HTML transformer state.
|
|
184
225
|
*
|
|
@@ -579,11 +620,15 @@ class IntegrationRenderer {
|
|
|
579
620
|
return {
|
|
580
621
|
name: this.name,
|
|
581
622
|
resolveRouteRenderInputs: (routeOptions) => this.resolveRouteRenderInputs(routeOptions),
|
|
582
|
-
|
|
623
|
+
resolveRouteDependencies: (input) => this.resolveRouteDependencies(input),
|
|
624
|
+
collectPageBrowserGraphContribution: async (routeFile) => {
|
|
625
|
+
const pageModule = await this.importPageFile(routeFile);
|
|
626
|
+
return await this.collectPageBrowserGraphContribution({ file: routeFile, pageModule });
|
|
627
|
+
},
|
|
583
628
|
resolveRoutePageComponentRender: (input) => this.resolveRoutePageComponentRender(input),
|
|
584
629
|
renderRouteBody: (renderOptions) => this.renderRouteBody(renderOptions),
|
|
585
630
|
getRouteHtmlFinalization: (renderOptions) => this.getRouteHtmlFinalization(renderOptions),
|
|
586
|
-
transformRouteResponse: (response) => this.transformRouteResponse(response)
|
|
631
|
+
transformRouteResponse: (response, htmlContributions) => this.transformRouteResponse(response, htmlContributions)
|
|
587
632
|
};
|
|
588
633
|
}
|
|
589
634
|
async resolveRouteRenderInputs(routeOptions) {
|
|
@@ -607,10 +652,9 @@ class IntegrationRenderer {
|
|
|
607
652
|
integrationSpecificProps
|
|
608
653
|
};
|
|
609
654
|
}
|
|
610
|
-
async
|
|
655
|
+
async resolveRouteDependencies(input) {
|
|
611
656
|
return {
|
|
612
|
-
resolvedDependencies: await this.resolveDependencies(input.components)
|
|
613
|
-
pageBrowserGraph: await this.buildPageBrowserGraph(input.routeOptions.file)
|
|
657
|
+
resolvedDependencies: await this.resolveDependencies(input.components)
|
|
614
658
|
};
|
|
615
659
|
}
|
|
616
660
|
async resolveRoutePageComponentRender(input) {
|
|
@@ -635,11 +679,13 @@ class IntegrationRenderer {
|
|
|
635
679
|
getRouteHtmlFinalization(renderOptions) {
|
|
636
680
|
const componentRootAttributes = renderOptions.componentRender?.canAttachAttributes && renderOptions.componentRender.rootAttributes && Object.keys(renderOptions.componentRender.rootAttributes).length > 0 ? renderOptions.componentRender.rootAttributes : void 0;
|
|
637
681
|
const documentAttributes = this.getDocumentAttributes(renderOptions);
|
|
682
|
+
const htmlContributions = this.getHtmlDocumentContributions({ renderOptions, partial: false });
|
|
638
683
|
const hasStructuralFinalization = componentRootAttributes && Object.keys(componentRootAttributes).length > 0 || documentAttributes && Object.keys(documentAttributes).length > 0;
|
|
639
|
-
if (!hasStructuralFinalization) {
|
|
684
|
+
if (!hasStructuralFinalization && (!htmlContributions || htmlContributions.length === 0)) {
|
|
640
685
|
return {};
|
|
641
686
|
}
|
|
642
687
|
return {
|
|
688
|
+
htmlContributions,
|
|
643
689
|
finalizeHtml: (html) => {
|
|
644
690
|
let renderedHtml = html;
|
|
645
691
|
if (componentRootAttributes) {
|
|
@@ -655,8 +701,8 @@ class IntegrationRenderer {
|
|
|
655
701
|
}
|
|
656
702
|
};
|
|
657
703
|
}
|
|
658
|
-
async transformRouteResponse(response) {
|
|
659
|
-
const transformedResponse = await this.htmlTransformer.transform(response);
|
|
704
|
+
async transformRouteResponse(response, htmlContributions) {
|
|
705
|
+
const transformedResponse = await this.htmlTransformer.transform(response, htmlContributions);
|
|
660
706
|
return transformedResponse.body ?? await transformedResponse.text();
|
|
661
707
|
}
|
|
662
708
|
/**
|
|
@@ -726,10 +772,14 @@ class IntegrationRenderer {
|
|
|
726
772
|
if (!shouldTransform) {
|
|
727
773
|
return html;
|
|
728
774
|
}
|
|
775
|
+
const htmlContributions = options.htmlContributions ?? this.getHtmlDocumentContributions({
|
|
776
|
+
partial: options.partial
|
|
777
|
+
});
|
|
729
778
|
const transformedResponse = await this.htmlTransformer.transform(
|
|
730
779
|
new Response(html, {
|
|
731
780
|
headers: { "Content-Type": "text/html" }
|
|
732
|
-
})
|
|
781
|
+
}),
|
|
782
|
+
htmlContributions
|
|
733
783
|
);
|
|
734
784
|
return await transformedResponse.text();
|
|
735
785
|
}
|
|
@@ -742,6 +792,18 @@ class IntegrationRenderer {
|
|
|
742
792
|
getDocumentAttributes(_renderOptions) {
|
|
743
793
|
return void 0;
|
|
744
794
|
}
|
|
795
|
+
/**
|
|
796
|
+
* Returns declarative HTML fragments that core should inject into the final document.
|
|
797
|
+
*
|
|
798
|
+
* @remarks
|
|
799
|
+
* Integrations may contribute document markup here, but core retains ownership
|
|
800
|
+
* of the final HTML rewrite pipeline and placement semantics. This is the
|
|
801
|
+
* supported document-markup extension point for integrations instead of custom
|
|
802
|
+
* response finalization logic.
|
|
803
|
+
*/
|
|
804
|
+
getHtmlDocumentContributions(_options) {
|
|
805
|
+
return void 0;
|
|
806
|
+
}
|
|
745
807
|
/**
|
|
746
808
|
* Returns a renderer instance for a given integration name.
|
|
747
809
|
*
|
|
@@ -883,13 +945,17 @@ class IntegrationRenderer {
|
|
|
883
945
|
return rootTag?.[1];
|
|
884
946
|
}
|
|
885
947
|
/**
|
|
886
|
-
*
|
|
887
|
-
*
|
|
948
|
+
* Collects declarative Page Browser Graph contributions for one Page.
|
|
949
|
+
*
|
|
950
|
+
* @remarks
|
|
951
|
+
* Integrations may describe page-scoped browser requirements here, while core
|
|
952
|
+
* retains ownership of dependency processing and final graph assembly. This is
|
|
953
|
+
* the supported page-browser extension point for integrations.
|
|
888
954
|
*
|
|
889
|
-
* @param
|
|
890
|
-
* @returns
|
|
955
|
+
* @param context - The route file path and already imported page module.
|
|
956
|
+
* @returns Declarative dependencies or pre-resolved assets for the Page.
|
|
891
957
|
*/
|
|
892
|
-
async
|
|
958
|
+
async collectPageBrowserGraphContribution(_context) {
|
|
893
959
|
return void 0;
|
|
894
960
|
}
|
|
895
961
|
/**
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
function buildProcessedAssetDedupeKey(asset) {
|
|
2
|
+
return [
|
|
3
|
+
asset.kind,
|
|
4
|
+
asset.position ?? "",
|
|
5
|
+
asset.srcUrl ?? "",
|
|
6
|
+
asset.filepath ?? "",
|
|
7
|
+
asset.content ?? "",
|
|
8
|
+
asset.inline ? "inline" : "external",
|
|
9
|
+
asset.excludeFromHtml ? "excluded" : "included",
|
|
10
|
+
asset.packageRole ?? "",
|
|
11
|
+
JSON.stringify(asset.attributes ?? {})
|
|
12
|
+
].join("|");
|
|
13
|
+
}
|
|
1
14
|
function dedupeProcessedAssets(assets) {
|
|
2
15
|
const unique = /* @__PURE__ */ new Map();
|
|
3
16
|
for (const asset of assets) {
|
|
4
|
-
const key =
|
|
5
|
-
asset.kind,
|
|
6
|
-
asset.position ?? "",
|
|
7
|
-
asset.srcUrl ?? "",
|
|
8
|
-
asset.filepath ?? "",
|
|
9
|
-
asset.content ?? "",
|
|
10
|
-
asset.inline ? "inline" : "external",
|
|
11
|
-
asset.excludeFromHtml ? "excluded" : "included",
|
|
12
|
-
asset.packageRole ?? "",
|
|
13
|
-
JSON.stringify(asset.attributes ?? {})
|
|
14
|
-
].join("|");
|
|
17
|
+
const key = buildProcessedAssetDedupeKey(asset);
|
|
15
18
|
if (!unique.has(key)) {
|
|
16
19
|
unique.set(key, asset);
|
|
17
20
|
}
|
|
@@ -19,5 +22,6 @@ function dedupeProcessedAssets(assets) {
|
|
|
19
22
|
return [...unique.values()];
|
|
20
23
|
}
|
|
21
24
|
export {
|
|
25
|
+
buildProcessedAssetDedupeKey,
|
|
22
26
|
dedupeProcessedAssets
|
|
23
27
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { EcoPagesAppConfig } from '../../types/internal-types.js';
|
|
2
|
-
import type { ComponentRenderResult, EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
|
|
2
|
+
import type { ComponentRenderResult, EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
|
|
3
3
|
import { type AssetProcessingService, type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
|
|
4
|
+
import type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js';
|
|
4
5
|
import { OwnershipValidationService } from './ownership-validation.service.js';
|
|
5
6
|
import { OwnershipPlanningService } from './ownership-planning.service.js';
|
|
6
7
|
export type RouteRenderOrchestratorResolvedInputs = {
|
|
@@ -11,9 +12,8 @@ export type RouteRenderOrchestratorResolvedInputs = {
|
|
|
11
12
|
metadata: PageMetadataProps;
|
|
12
13
|
integrationSpecificProps: Record<string, unknown>;
|
|
13
14
|
};
|
|
14
|
-
export type
|
|
15
|
+
export type RouteRenderOrchestratorResolvedDependencies = {
|
|
15
16
|
resolvedDependencies: ProcessedAsset[];
|
|
16
|
-
pageBrowserGraph?: PageBrowserGraphResult;
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
19
|
* Structural HTML work applied after the route body has been fully resolved.
|
|
@@ -23,6 +23,7 @@ export type RouteRenderOrchestratorResolvedAssets = {
|
|
|
23
23
|
*/
|
|
24
24
|
export type RouteHtmlFinalization = {
|
|
25
25
|
finalizeHtml?(html: string): string;
|
|
26
|
+
htmlContributions?: HtmlDocumentContribution[];
|
|
26
27
|
};
|
|
27
28
|
export interface RouteRenderOrchestratorAdapter<C> {
|
|
28
29
|
/**
|
|
@@ -34,12 +35,15 @@ export interface RouteRenderOrchestratorAdapter<C> {
|
|
|
34
35
|
*/
|
|
35
36
|
resolveRouteRenderInputs(routeOptions: RouteRendererOptions): Promise<RouteRenderOrchestratorResolvedInputs>;
|
|
36
37
|
/**
|
|
37
|
-
* Resolves route-owned
|
|
38
|
+
* Resolves route-owned dependencies needed before Integration rendering starts.
|
|
38
39
|
*/
|
|
39
|
-
|
|
40
|
-
routeOptions: RouteRendererOptions;
|
|
40
|
+
resolveRouteDependencies(input: {
|
|
41
41
|
components: (EcoComponent | Partial<EcoComponent>)[];
|
|
42
|
-
}): Promise<
|
|
42
|
+
}): Promise<RouteRenderOrchestratorResolvedDependencies>;
|
|
43
|
+
/**
|
|
44
|
+
* Collects declarative Page Browser Graph requirements for one route.
|
|
45
|
+
*/
|
|
46
|
+
collectPageBrowserGraphContribution(routeFile: string): Promise<PageBrowserGraphContribution | undefined>;
|
|
43
47
|
/**
|
|
44
48
|
* Resolves the optional page-root render through the foreign-child-aware component contract.
|
|
45
49
|
*/
|
|
@@ -60,7 +64,7 @@ export interface RouteRenderOrchestratorAdapter<C> {
|
|
|
60
64
|
/**
|
|
61
65
|
* Runs SSR-policy response transformation and returns the body value exposed to callers.
|
|
62
66
|
*/
|
|
63
|
-
transformRouteResponse(response: Response): Promise<RouteRendererBody>;
|
|
67
|
+
transformRouteResponse(response: Response, htmlContributions?: HtmlDocumentContribution[]): Promise<RouteRendererBody>;
|
|
64
68
|
}
|
|
65
69
|
/**
|
|
66
70
|
* Captured route-render output in both replayable body and string HTML forms.
|
|
@@ -89,6 +93,7 @@ export declare class RouteRenderOrchestrator {
|
|
|
89
93
|
private readonly assetProcessingService;
|
|
90
94
|
private readonly ownershipPlanningService;
|
|
91
95
|
private readonly ownershipValidationService;
|
|
96
|
+
private readonly pageBrowserGraphCache;
|
|
92
97
|
constructor(appConfig: EcoPagesAppConfig, assetProcessingService: AssetProcessingService, dependencies?: RouteRenderOrchestratorDependencies);
|
|
93
98
|
/**
|
|
94
99
|
* Builds normalized route render options before the integration render runs.
|
|
@@ -98,6 +103,15 @@ export declare class RouteRenderOrchestrator {
|
|
|
98
103
|
* produces the page package consumed by downstream HTML transformation.
|
|
99
104
|
*/
|
|
100
105
|
prepareRenderOptions<C = unknown>(routeOptions: RouteRendererOptions, adapter: RouteRenderOrchestratorAdapter<C>): Promise<IntegrationRendererRenderOptions<C>>;
|
|
106
|
+
resolveDeclaredPageBrowserGraph(input: {
|
|
107
|
+
routeFile: string;
|
|
108
|
+
integrationName: string;
|
|
109
|
+
collectContribution: () => Promise<PageBrowserGraphContribution | undefined>;
|
|
110
|
+
}): Promise<PageBrowserGraphResult | undefined>;
|
|
111
|
+
private resolvePageBrowserGraph;
|
|
112
|
+
private isHmrEnabled;
|
|
113
|
+
private buildPageBrowserGraph;
|
|
114
|
+
private partitionPageBrowserGraphAssets;
|
|
101
115
|
/**
|
|
102
116
|
* Captures one route render body as HTML while preserving a replayable body value.
|
|
103
117
|
*/
|
|
@@ -44,6 +44,7 @@ class RouteRenderOrchestrator {
|
|
|
44
44
|
assetProcessingService;
|
|
45
45
|
ownershipPlanningService;
|
|
46
46
|
ownershipValidationService;
|
|
47
|
+
pageBrowserGraphCache = /* @__PURE__ */ new Map();
|
|
47
48
|
constructor(appConfig, assetProcessingService, dependencies = {}) {
|
|
48
49
|
this.appConfig = appConfig;
|
|
49
50
|
this.assetProcessingService = assetProcessingService;
|
|
@@ -77,16 +78,16 @@ class RouteRenderOrchestrator {
|
|
|
77
78
|
validationErrors
|
|
78
79
|
});
|
|
79
80
|
const componentsToResolve = Layout ? [HtmlTemplate, Layout, Page] : [HtmlTemplate, Page];
|
|
80
|
-
const { resolvedDependencies
|
|
81
|
-
routeOptions,
|
|
81
|
+
const { resolvedDependencies } = await adapter.resolveRouteDependencies({
|
|
82
82
|
components: componentsToResolve
|
|
83
83
|
});
|
|
84
|
+
const pageBrowserGraph = await this.resolvePageBrowserGraph({
|
|
85
|
+
routeFile: routeOptions.file,
|
|
86
|
+
integrationName: adapter.name,
|
|
87
|
+
collectContribution: async () => await adapter.collectPageBrowserGraphContribution(routeOptions.file)
|
|
88
|
+
});
|
|
84
89
|
const usedIntegrationDependencies = this.collectUsedIntegrationDependencies(componentsToResolve, adapter.name);
|
|
85
|
-
const allDependencies = [
|
|
86
|
-
...resolvedDependencies,
|
|
87
|
-
...usedIntegrationDependencies,
|
|
88
|
-
...pageBrowserGraph?.assets ?? []
|
|
89
|
-
];
|
|
90
|
+
const allDependencies = [...resolvedDependencies, ...usedIntegrationDependencies];
|
|
90
91
|
const componentRender = await adapter.resolveRoutePageComponentRender({
|
|
91
92
|
Page,
|
|
92
93
|
Layout,
|
|
@@ -106,7 +107,7 @@ class RouteRenderOrchestrator {
|
|
|
106
107
|
allDependencies.push(...eagerSsrLazyAssets);
|
|
107
108
|
}
|
|
108
109
|
const dedupedDependencies = dedupeProcessedAssets(allDependencies);
|
|
109
|
-
const pagePackage = createPagePackage(dedupedDependencies);
|
|
110
|
+
const pagePackage = createPagePackage(dedupedDependencies, { pageBrowserGraph });
|
|
110
111
|
const pageProps = {
|
|
111
112
|
...props,
|
|
112
113
|
params: routeOptions.params || {},
|
|
@@ -141,6 +142,52 @@ class RouteRenderOrchestrator {
|
|
|
141
142
|
...preparedOptions
|
|
142
143
|
};
|
|
143
144
|
}
|
|
145
|
+
async resolveDeclaredPageBrowserGraph(input) {
|
|
146
|
+
return await this.resolvePageBrowserGraph(input);
|
|
147
|
+
}
|
|
148
|
+
async resolvePageBrowserGraph(input) {
|
|
149
|
+
if (this.isHmrEnabled()) {
|
|
150
|
+
return await this.buildPageBrowserGraph(input);
|
|
151
|
+
}
|
|
152
|
+
const cacheKey = `${input.integrationName}:${input.routeFile}`;
|
|
153
|
+
const cachedGraph = this.pageBrowserGraphCache.get(cacheKey);
|
|
154
|
+
if (cachedGraph) {
|
|
155
|
+
return await cachedGraph;
|
|
156
|
+
}
|
|
157
|
+
const pendingGraph = this.buildPageBrowserGraph(input).catch((error) => {
|
|
158
|
+
this.pageBrowserGraphCache.delete(cacheKey);
|
|
159
|
+
throw error;
|
|
160
|
+
});
|
|
161
|
+
this.pageBrowserGraphCache.set(cacheKey, pendingGraph);
|
|
162
|
+
return await pendingGraph;
|
|
163
|
+
}
|
|
164
|
+
isHmrEnabled() {
|
|
165
|
+
return typeof this.assetProcessingService.getHmrManager === "function" && this.assetProcessingService.getHmrManager()?.isEnabled() === true;
|
|
166
|
+
}
|
|
167
|
+
async buildPageBrowserGraph(input) {
|
|
168
|
+
const contribution = await input.collectContribution();
|
|
169
|
+
if (!contribution) {
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
const processedDependencies = contribution.dependencies?.length ? await this.assetProcessingService.processDependencies(
|
|
173
|
+
contribution.dependencies,
|
|
174
|
+
`${input.integrationName}:${input.routeFile}`
|
|
175
|
+
) : [];
|
|
176
|
+
const resolvedAssets = [...processedDependencies, ...contribution.assets ?? []];
|
|
177
|
+
return this.partitionPageBrowserGraphAssets(resolvedAssets);
|
|
178
|
+
}
|
|
179
|
+
partitionPageBrowserGraphAssets(assets) {
|
|
180
|
+
const entryAssets = [];
|
|
181
|
+
const chunkAssets = [];
|
|
182
|
+
for (const asset of assets) {
|
|
183
|
+
if (asset.packageRole === "dynamic-chunk") {
|
|
184
|
+
chunkAssets.push(asset);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
entryAssets.push(asset);
|
|
188
|
+
}
|
|
189
|
+
return { entryAssets, chunkAssets };
|
|
190
|
+
}
|
|
144
191
|
/**
|
|
145
192
|
* Captures one route render body as HTML while preserving a replayable body value.
|
|
146
193
|
*/
|
|
@@ -179,7 +226,8 @@ class RouteRenderOrchestrator {
|
|
|
179
226
|
headers: {
|
|
180
227
|
"Content-Type": "text/html"
|
|
181
228
|
}
|
|
182
|
-
})
|
|
229
|
+
}),
|
|
230
|
+
htmlFinalization.htmlContributions
|
|
183
231
|
);
|
|
184
232
|
return {
|
|
185
233
|
body: body2,
|
|
@@ -192,7 +240,8 @@ class RouteRenderOrchestrator {
|
|
|
192
240
|
headers: {
|
|
193
241
|
"Content-Type": "text/html"
|
|
194
242
|
}
|
|
195
|
-
})
|
|
243
|
+
}),
|
|
244
|
+
htmlFinalization.htmlContributions
|
|
196
245
|
);
|
|
197
246
|
return {
|
|
198
247
|
body,
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { PagePackageResult } from '../../../types/public-types.js';
|
|
2
2
|
import type { ProcessedAsset } from './assets.types.js';
|
|
3
|
-
|
|
3
|
+
import type { PageBrowserGraphResult } from '../../../types/public-types.js';
|
|
4
|
+
export declare function createPagePackage(assets: ProcessedAsset[], options?: {
|
|
5
|
+
pageBrowserGraph?: PageBrowserGraphResult;
|
|
6
|
+
}): PagePackageResult;
|
|
@@ -9,14 +9,19 @@ function getSuppressedSourceFilepaths(assets) {
|
|
|
9
9
|
}
|
|
10
10
|
return suppressed;
|
|
11
11
|
}
|
|
12
|
-
function createPagePackage(assets) {
|
|
12
|
+
function createPagePackage(assets, options = {}) {
|
|
13
|
+
const allAssets = [
|
|
14
|
+
...assets,
|
|
15
|
+
...options.pageBrowserGraph?.entryAssets ?? [],
|
|
16
|
+
...options.pageBrowserGraph?.chunkAssets ?? []
|
|
17
|
+
];
|
|
13
18
|
const inlineAssets = [];
|
|
14
19
|
const separateAssets = [];
|
|
15
20
|
const dynamicChunks = [];
|
|
16
21
|
let pageScript;
|
|
17
22
|
let pageStylesheet;
|
|
18
|
-
const suppressedSourceFilepaths = getSuppressedSourceFilepaths(
|
|
19
|
-
for (const asset of
|
|
23
|
+
const suppressedSourceFilepaths = getSuppressedSourceFilepaths(allAssets);
|
|
24
|
+
for (const asset of allAssets) {
|
|
20
25
|
if (asset.inline) {
|
|
21
26
|
inlineAssets.push(asset);
|
|
22
27
|
continue;
|
|
@@ -48,8 +53,9 @@ function createPagePackage(assets) {
|
|
|
48
53
|
separateAssets.push(asset);
|
|
49
54
|
}
|
|
50
55
|
return {
|
|
51
|
-
assets,
|
|
52
|
-
|
|
56
|
+
assets: allAssets,
|
|
57
|
+
pageBrowserGraph: options.pageBrowserGraph,
|
|
58
|
+
htmlAssets: allAssets.filter((asset) => shouldIncludeInHtml(asset, suppressedSourceFilepaths)),
|
|
53
59
|
pageScript,
|
|
54
60
|
pageStylesheet,
|
|
55
61
|
inlineAssets,
|
|
@@ -78,7 +78,9 @@ class NodeModuleScriptProcessor extends BaseScriptProcessor {
|
|
|
78
78
|
*/
|
|
79
79
|
resolveModulePathFallback(importPath, rootDir, maxDepth = 5) {
|
|
80
80
|
try {
|
|
81
|
-
return fileURLToPath(
|
|
81
|
+
return fileURLToPath(
|
|
82
|
+
import.meta.resolve(importPath, pathToFileURL(path.join(rootDir, "package.json")).href)
|
|
83
|
+
);
|
|
82
84
|
} catch {
|
|
83
85
|
}
|
|
84
86
|
let currentDir = rootDir;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ProcessedAsset } from '../assets/asset-processing-service/assets.types.js';
|
|
2
2
|
import type { PagePackageResult } from '../../types/public-types.js';
|
|
3
3
|
import { type HtmlRewriterMode, type HtmlRewriterProvider } from './html-rewriter-provider.service.js';
|
|
4
|
+
export type HtmlDocumentContributionPlacement = 'head-prepend' | 'head-append' | 'body-prepend' | 'body-append';
|
|
5
|
+
export type HtmlDocumentContribution = {
|
|
6
|
+
placement: HtmlDocumentContributionPlacement;
|
|
7
|
+
html: string;
|
|
8
|
+
};
|
|
4
9
|
export interface HtmlTransformerServiceOptions {
|
|
5
10
|
htmlRewriterMode?: HtmlRewriterMode;
|
|
6
11
|
htmlRewriterProvider?: HtmlRewriterProvider;
|
|
@@ -24,11 +29,14 @@ export declare class HtmlTransformerService {
|
|
|
24
29
|
private generateStylesheetTag;
|
|
25
30
|
private appendDependencies;
|
|
26
31
|
private buildDependencyTags;
|
|
32
|
+
private applyContributions;
|
|
27
33
|
/**
|
|
28
34
|
* Injects generated markup immediately before the closing HTML tag when it is
|
|
29
35
|
* present, or appends/prepends a fallback insertion otherwise.
|
|
30
36
|
*/
|
|
31
37
|
private injectBeforeClosingTag;
|
|
38
|
+
private injectAfterOpeningTag;
|
|
39
|
+
private groupContributionsByPlacement;
|
|
32
40
|
/**
|
|
33
41
|
* Replaces the current processed dependency set used during HTML finalization.
|
|
34
42
|
*/
|
|
@@ -75,11 +83,12 @@ export declare class HtmlTransformerService {
|
|
|
75
83
|
* string-based fallback remains in place for runtimes that cannot provide one
|
|
76
84
|
* of those rewriter implementations.
|
|
77
85
|
*/
|
|
78
|
-
transform(res: Response): Promise<Response>;
|
|
86
|
+
transform(res: Response, contributions?: HtmlDocumentContribution[]): Promise<Response>;
|
|
79
87
|
/**
|
|
80
88
|
* Splits processed assets into head and body injection groups.
|
|
81
89
|
*/
|
|
82
90
|
private groupDependenciesByPosition;
|
|
91
|
+
private resolvePagePackageHtmlDependencies;
|
|
83
92
|
/**
|
|
84
93
|
* Builds a serialized HTML attribute string from an attribute object.
|
|
85
94
|
*/
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DefaultHtmlRewriterProvider
|
|
3
3
|
} from "./html-rewriter-provider.service.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildProcessedAssetDedupeKey,
|
|
6
|
+
dedupeProcessedAssets
|
|
7
|
+
} from "../../route-renderer/orchestration/processed-asset-dedupe.js";
|
|
5
8
|
class HtmlTransformerService {
|
|
6
9
|
processedDependencies = [];
|
|
7
10
|
pagePackage;
|
|
@@ -44,6 +47,11 @@ class HtmlTransformerService {
|
|
|
44
47
|
(dep) => dep.kind === "script" ? this.generateScriptTag(dep) : this.generateStylesheetTag(dep)
|
|
45
48
|
).join("");
|
|
46
49
|
}
|
|
50
|
+
applyContributions(element, contributions, placement) {
|
|
51
|
+
for (const contribution of contributions) {
|
|
52
|
+
element[placement](contribution.html, { html: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
47
55
|
/**
|
|
48
56
|
* Injects generated markup immediately before the closing HTML tag when it is
|
|
49
57
|
* present, or appends/prepends a fallback insertion otherwise.
|
|
@@ -63,6 +71,26 @@ class HtmlTransformerService {
|
|
|
63
71
|
}
|
|
64
72
|
return `${html}${content}`;
|
|
65
73
|
}
|
|
74
|
+
injectAfterOpeningTag(html, tag, content) {
|
|
75
|
+
if (!content) {
|
|
76
|
+
return html;
|
|
77
|
+
}
|
|
78
|
+
const openingTag = new RegExp(`<${tag}\\b[^>]*>`, "i");
|
|
79
|
+
const match = html.match(openingTag);
|
|
80
|
+
if (!match || match.index === void 0) {
|
|
81
|
+
return tag === "head" ? `${content}${html}` : html.replace(/<body\b[^>]*>/i, (value) => `${value}${content}`);
|
|
82
|
+
}
|
|
83
|
+
const insertAt = match.index + match[0].length;
|
|
84
|
+
return `${html.slice(0, insertAt)}${content}${html.slice(insertAt)}`;
|
|
85
|
+
}
|
|
86
|
+
groupContributionsByPlacement(contributions) {
|
|
87
|
+
return {
|
|
88
|
+
headPrepend: contributions.filter((item) => item.placement === "head-prepend"),
|
|
89
|
+
headAppend: contributions.filter((item) => item.placement === "head-append"),
|
|
90
|
+
bodyPrepend: contributions.filter((item) => item.placement === "body-prepend"),
|
|
91
|
+
bodyAppend: contributions.filter((item) => item.placement === "body-append")
|
|
92
|
+
};
|
|
93
|
+
}
|
|
66
94
|
/**
|
|
67
95
|
* Replaces the current processed dependency set used during HTML finalization.
|
|
68
96
|
*/
|
|
@@ -75,7 +103,7 @@ class HtmlTransformerService {
|
|
|
75
103
|
*/
|
|
76
104
|
setPagePackage(pagePackage) {
|
|
77
105
|
this.pagePackage = pagePackage;
|
|
78
|
-
this.processedDependencies = pagePackage
|
|
106
|
+
this.processedDependencies = this.resolvePagePackageHtmlDependencies(pagePackage);
|
|
79
107
|
}
|
|
80
108
|
/**
|
|
81
109
|
* Returns the processed dependencies queued for the next transform pass.
|
|
@@ -160,24 +188,47 @@ class HtmlTransformerService {
|
|
|
160
188
|
* string-based fallback remains in place for runtimes that cannot provide one
|
|
161
189
|
* of those rewriter implementations.
|
|
162
190
|
*/
|
|
163
|
-
async transform(res) {
|
|
191
|
+
async transform(res, contributions = []) {
|
|
164
192
|
const { head, body } = this.groupDependenciesByPosition();
|
|
193
|
+
const { headPrepend, headAppend, bodyPrepend, bodyAppend } = this.groupContributionsByPlacement(contributions);
|
|
165
194
|
const htmlRewriter = await this.htmlRewriterProvider.createHtmlRewriter();
|
|
166
195
|
if (htmlRewriter) {
|
|
167
196
|
htmlRewriter.on("head", {
|
|
168
|
-
element: (element) =>
|
|
197
|
+
element: (element) => {
|
|
198
|
+
this.applyContributions(element, headPrepend, "prepend");
|
|
199
|
+
this.appendDependencies(element, head);
|
|
200
|
+
this.applyContributions(element, headAppend, "append");
|
|
201
|
+
}
|
|
169
202
|
}).on("body", {
|
|
170
|
-
element: (element) =>
|
|
203
|
+
element: (element) => {
|
|
204
|
+
this.applyContributions(element, bodyPrepend, "prepend");
|
|
205
|
+
this.appendDependencies(element, body);
|
|
206
|
+
this.applyContributions(element, bodyAppend, "append");
|
|
207
|
+
}
|
|
171
208
|
});
|
|
172
209
|
return htmlRewriter.transform(res);
|
|
173
210
|
}
|
|
174
211
|
const html = await res.text();
|
|
175
212
|
const headers = new Headers(res.headers);
|
|
176
|
-
const
|
|
177
|
-
|
|
213
|
+
const withHeadPrependedContent = this.injectAfterOpeningTag(
|
|
214
|
+
html,
|
|
215
|
+
"head",
|
|
216
|
+
headPrepend.map((item) => item.html).join("")
|
|
217
|
+
);
|
|
218
|
+
const withHeadDependencies = this.injectBeforeClosingTag(
|
|
219
|
+
withHeadPrependedContent,
|
|
220
|
+
"head",
|
|
221
|
+
`${this.buildDependencyTags(head)}${headAppend.map((item) => item.html).join("")}`
|
|
222
|
+
);
|
|
223
|
+
const withBodyPrependedContent = this.injectAfterOpeningTag(
|
|
178
224
|
withHeadDependencies,
|
|
179
225
|
"body",
|
|
180
|
-
|
|
226
|
+
bodyPrepend.map((item) => item.html).join("")
|
|
227
|
+
);
|
|
228
|
+
const transformedHtml = this.injectBeforeClosingTag(
|
|
229
|
+
withBodyPrependedContent,
|
|
230
|
+
"body",
|
|
231
|
+
`${this.buildDependencyTags(body)}${bodyAppend.map((item) => item.html).join("")}`
|
|
181
232
|
);
|
|
182
233
|
return new Response(transformedHtml, {
|
|
183
234
|
headers,
|
|
@@ -189,7 +240,7 @@ class HtmlTransformerService {
|
|
|
189
240
|
* Splits processed assets into head and body injection groups.
|
|
190
241
|
*/
|
|
191
242
|
groupDependenciesByPosition() {
|
|
192
|
-
const dependencies = this.pagePackage
|
|
243
|
+
const dependencies = this.pagePackage ? this.resolvePagePackageHtmlDependencies(this.pagePackage) : this.processedDependencies;
|
|
193
244
|
return dependencies.reduce(
|
|
194
245
|
(acc, dep) => {
|
|
195
246
|
if (dep.kind === "script") {
|
|
@@ -204,6 +255,26 @@ class HtmlTransformerService {
|
|
|
204
255
|
{ head: [], body: [] }
|
|
205
256
|
);
|
|
206
257
|
}
|
|
258
|
+
resolvePagePackageHtmlDependencies(pagePackage) {
|
|
259
|
+
if (!pagePackage.pageBrowserGraph) {
|
|
260
|
+
return pagePackage.htmlAssets;
|
|
261
|
+
}
|
|
262
|
+
const chunkKeys = new Set(
|
|
263
|
+
pagePackage.pageBrowserGraph.chunkAssets.map((asset) => buildProcessedAssetDedupeKey(asset))
|
|
264
|
+
);
|
|
265
|
+
const graphKeys = new Set(
|
|
266
|
+
[...pagePackage.pageBrowserGraph.entryAssets, ...pagePackage.pageBrowserGraph.chunkAssets].map(
|
|
267
|
+
(asset) => buildProcessedAssetDedupeKey(asset)
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
const nonGraphHtmlAssets = pagePackage.htmlAssets.filter(
|
|
271
|
+
(asset) => !graphKeys.has(buildProcessedAssetDedupeKey(asset))
|
|
272
|
+
);
|
|
273
|
+
const entryHtmlAssets = pagePackage.pageBrowserGraph.entryAssets.filter(
|
|
274
|
+
(asset) => !chunkKeys.has(buildProcessedAssetDedupeKey(asset))
|
|
275
|
+
);
|
|
276
|
+
return dedupeProcessedAssets([...nonGraphHtmlAssets, ...entryHtmlAssets]);
|
|
277
|
+
}
|
|
207
278
|
/**
|
|
208
279
|
* Builds a serialized HTML attribute string from an attribute object.
|
|
209
280
|
*/
|