@ecopages/core 0.2.0-alpha.25 → 0.2.0-alpha.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -7
- package/package.json +4 -47
- package/src/adapters/bun/create-app.ts +54 -2
- package/src/adapters/bun/hmr-manager.test.ts +0 -2
- package/src/adapters/bun/hmr-manager.ts +1 -24
- package/src/adapters/bun/server-adapter.ts +30 -4
- package/src/adapters/node/node-hmr-manager.test.ts +0 -2
- package/src/adapters/node/node-hmr-manager.ts +2 -25
- package/src/adapters/shared/explicit-static-render-preparation.ts +58 -0
- package/src/adapters/shared/explicit-static-route-matcher.test.ts +6 -6
- package/src/adapters/shared/explicit-static-route-matcher.ts +22 -31
- package/src/adapters/shared/file-route-middleware-pipeline.test.ts +5 -10
- package/src/adapters/shared/file-route-middleware-pipeline.ts +8 -17
- package/src/adapters/shared/fs-server-response-factory.test.ts +32 -43
- package/src/adapters/shared/fs-server-response-factory.ts +15 -37
- package/src/adapters/shared/fs-server-response-matcher.test.ts +65 -39
- package/src/adapters/shared/fs-server-response-matcher.ts +94 -43
- package/src/adapters/shared/hmr-manager.contract.test.ts +0 -4
- package/src/adapters/shared/render-context.ts +3 -3
- package/src/adapters/shared/server-adapter.test.ts +53 -0
- package/src/adapters/shared/server-adapter.ts +228 -159
- package/src/adapters/shared/server-route-handler.test.ts +6 -5
- package/src/adapters/shared/server-route-handler.ts +4 -4
- package/src/adapters/shared/server-static-builder.test.ts +4 -4
- package/src/adapters/shared/server-static-builder.ts +4 -4
- package/src/config/README.md +1 -1
- package/src/config/config-builder.test.ts +0 -1
- package/src/config/config-builder.ts +2 -7
- package/src/dev/host-runtime.ts +34 -0
- package/src/eco/eco.browser.test.ts +2 -2
- package/src/eco/eco.browser.ts +2 -2
- package/src/eco/eco.test.ts +6 -6
- package/src/eco/eco.ts +12 -12
- package/src/eco/eco.types.ts +3 -3
- package/src/errors/index.ts +1 -0
- package/src/hmr/client/hmr-runtime.ts +4 -2
- package/src/hmr/strategies/js-hmr-strategy.test.ts +0 -1
- package/src/hmr/strategies/js-hmr-strategy.ts +0 -6
- package/src/integrations/ghtml/ghtml-renderer.test.ts +7 -7
- package/src/integrations/ghtml/ghtml-renderer.ts +1 -11
- package/src/plugins/eco-component-meta-plugin.ts +0 -1
- package/src/plugins/integration-plugin.test.ts +9 -14
- package/src/plugins/integration-plugin.ts +34 -22
- package/src/plugins/processor.ts +17 -0
- package/src/route-renderer/GRAPH.md +81 -289
- package/src/route-renderer/README.md +67 -105
- package/src/route-renderer/orchestration/component-render-context.ts +45 -38
- package/src/route-renderer/orchestration/declared-ownership-graph.ts +62 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.ts +383 -0
- package/src/route-renderer/orchestration/integration-renderer.test.ts +118 -121
- package/src/route-renderer/orchestration/integration-renderer.ts +362 -403
- package/src/route-renderer/orchestration/ownership-planning.service.ts +97 -0
- package/src/route-renderer/orchestration/ownership-validation.service.ts +76 -0
- package/src/route-renderer/orchestration/processed-asset-dedupe.ts +1 -1
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.test.ts → queued-foreign-subtree-resolution.service.test.ts} +76 -71
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.ts → queued-foreign-subtree-resolution.service.ts} +68 -63
- package/src/route-renderer/orchestration/render-output.utils.ts +21 -13
- package/src/route-renderer/orchestration/{render-preparation.service.test.ts → route-render-orchestrator.prepare-render-options.test.ts} +160 -85
- package/src/route-renderer/orchestration/route-render-orchestrator.test.ts +265 -0
- package/src/route-renderer/orchestration/{render-preparation.service.ts → route-render-orchestrator.ts} +244 -160
- package/src/route-renderer/page-loading/component-dependency-collection.ts +9 -3
- package/src/route-renderer/page-loading/declared-asset-collection.ts +2 -5
- package/src/route-renderer/page-loading/dependency-resolver.test.ts +107 -11
- package/src/route-renderer/page-loading/dependency-resolver.ts +6 -12
- package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +1 -1
- package/src/route-renderer/page-loading/lazy-entry-collection.ts +1 -1
- package/src/route-renderer/page-loading/lazy-trigger-planning.ts +1 -1
- package/src/route-renderer/page-loading/module-declaration-aggregation.ts +1 -1
- package/src/route-renderer/page-loading/module-declaration-scripts.ts +1 -1
- package/src/route-renderer/page-loading/page-dependency-bundling.ts +105 -66
- package/src/route-renderer/route-renderer.ts +28 -31
- package/src/router/README.md +16 -19
- package/src/router/server/route-registry.test.ts +176 -0
- package/src/router/server/route-registry.ts +382 -0
- package/src/services/README.md +1 -2
- package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +1 -1
- package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +1 -4
- package/src/services/assets/asset-processing-service/asset-processing.service.ts +1 -2
- package/src/services/assets/asset-processing-service/assets.types.ts +3 -0
- package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +1 -1
- package/src/services/assets/asset-processing-service/index.ts +1 -0
- package/src/{route-renderer/orchestration/page-packaging.service.test.ts → services/assets/asset-processing-service/page-package.test.ts} +38 -14
- package/src/services/assets/asset-processing-service/page-package.ts +93 -0
- package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +4 -5
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +13 -10
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +3 -0
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +6 -0
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +2 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +1 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +2 -0
- package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +1 -1
- package/src/services/html/html-transformer.service.test.ts +1 -4
- package/src/services/module-loading/app-server-module-transpiler.service.ts +1 -3
- package/src/services/module-loading/node-bootstrap-plugin.ts +17 -3
- package/src/services/module-loading/page-module-import.service.ts +0 -1
- package/src/services/module-loading/source-module-support.ts +1 -1
- package/src/static-site-generator/static-site-generator.test.ts +124 -32
- package/src/static-site-generator/static-site-generator.ts +168 -185
- package/src/types/internal-types.ts +13 -12
- package/src/types/public-types.ts +55 -39
- package/src/watchers/project-watcher.test-helpers.ts +4 -3
- package/src/route-renderer/orchestration/boundary-planning.service.ts +0 -146
- package/src/route-renderer/orchestration/page-packaging.service.ts +0 -85
- package/src/route-renderer/orchestration/render-execution.service.test.ts +0 -196
- package/src/route-renderer/orchestration/render-execution.service.ts +0 -182
- package/src/route-renderer/orchestration/route-shell-composer.service.ts +0 -162
- package/src/router/server/fs-router-scanner.test.ts +0 -83
- package/src/router/server/fs-router-scanner.ts +0 -224
- package/src/router/server/fs-router.test.ts +0 -214
- package/src/router/server/fs-router.ts +0 -122
- package/src/services/runtime-state/runtime-specifier-registry.service.ts +0 -96
|
@@ -3,12 +3,12 @@ import { eco } from '../../eco/eco.ts';
|
|
|
3
3
|
import { IntegrationRenderer, type RenderToResponseContext } from './integration-renderer.ts';
|
|
4
4
|
import type { EcoPagesAppConfig } from '../../types/internal-types.ts';
|
|
5
5
|
import type { AssetProcessingService, ProcessedAsset } from '../../services/assets/asset-processing-service/index.ts';
|
|
6
|
-
import { getComponentRenderContext, type
|
|
6
|
+
import { getComponentRenderContext, type ForeignChildRuntime } from './component-render-context.ts';
|
|
7
7
|
import type {
|
|
8
8
|
BaseIntegrationContext,
|
|
9
9
|
ComponentRenderInput,
|
|
10
10
|
ComponentRenderResult,
|
|
11
|
-
|
|
11
|
+
ForeignSubtreeRenderPayload,
|
|
12
12
|
RouteRendererBody,
|
|
13
13
|
EcoPagesElement,
|
|
14
14
|
IntegrationRendererRenderOptions,
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
import type { EcoPageComponent } from '../../eco/eco.types.ts';
|
|
21
21
|
import { runWithComponentRenderContext } from './component-render-context.ts';
|
|
22
22
|
|
|
23
|
-
function
|
|
23
|
+
function createUnresolvedMarkerArtifact(nodeId: string, componentRef: string, propsRef: string): string {
|
|
24
24
|
return `<eco-marker data-eco-node-id="${nodeId}" data-eco-component-ref="${componentRef}" data-eco-props-ref="${propsRef}"></eco-marker>`;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -29,8 +29,9 @@ function createBoundaryMarker(nodeId: string, componentRef: string, propsRef: st
|
|
|
29
29
|
*/
|
|
30
30
|
class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
31
31
|
name = 'test-renderer';
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
ForeignChildRuntimeCreationCount = 0;
|
|
33
|
+
ForeignChildRenderCount = 0;
|
|
34
|
+
UseFailFastForeignChildRuntime = false;
|
|
34
35
|
|
|
35
36
|
/** Mock data container for page module */
|
|
36
37
|
PageModule: EcoPageFile | null = null;
|
|
@@ -59,9 +60,9 @@ class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
|
59
60
|
return super.renderComponent(_input);
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
override async
|
|
63
|
-
this.
|
|
64
|
-
return await super.
|
|
63
|
+
override async renderComponentWithForeignChildren(input: ComponentRenderInput): Promise<ComponentRenderResult> {
|
|
64
|
+
this.ForeignChildRenderCount += 1;
|
|
65
|
+
return await super.renderComponentWithForeignChildren(input);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
async renderToResponse<P>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response> {
|
|
@@ -113,8 +114,8 @@ class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
|
113
114
|
return [];
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
protected override async
|
|
117
|
-
return [];
|
|
117
|
+
protected override async buildPageBrowserGraph(_file: string) {
|
|
118
|
+
return { assets: [] as ProcessedAsset[] };
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
/**
|
|
@@ -128,22 +129,12 @@ class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
|
128
129
|
return this.processComponentDependencies(components);
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
public
|
|
132
|
-
|
|
133
|
-
targetIntegration?: string;
|
|
134
|
-
}) {
|
|
135
|
-
return this.shouldResolveBoundaryInOwningRenderer(input);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
public testHasForeignBoundaryDescendants(component: EcoComponent) {
|
|
139
|
-
return this.hasForeignBoundaryDescendants(component);
|
|
132
|
+
public testShouldDelegateForeignChild(input: { currentIntegration: string; targetIntegration?: string }) {
|
|
133
|
+
return this.foreignSubtreeExecutionService.shouldDelegateForeignChild(input);
|
|
140
134
|
}
|
|
141
135
|
|
|
142
|
-
public
|
|
143
|
-
|
|
144
|
-
rendererCache = new Map<string, IntegrationRenderer<any>>(),
|
|
145
|
-
) {
|
|
146
|
-
return this.resolveBoundaryInOwningRenderer(input, rendererCache);
|
|
136
|
+
public testHasForeignChildDescendants(component: EcoComponent) {
|
|
137
|
+
return this.hasForeignChildDescendants(component);
|
|
147
138
|
}
|
|
148
139
|
|
|
149
140
|
public async testGetHtmlTemplate() {
|
|
@@ -195,16 +186,21 @@ class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
|
195
186
|
});
|
|
196
187
|
}
|
|
197
188
|
|
|
198
|
-
public async
|
|
199
|
-
return this.
|
|
189
|
+
public async testRenderForeignSubtree(input: ComponentRenderInput) {
|
|
190
|
+
return this.renderForeignSubtree(input);
|
|
200
191
|
}
|
|
201
192
|
|
|
202
|
-
protected override
|
|
203
|
-
|
|
193
|
+
protected override createForeignChildRuntime(options: {
|
|
194
|
+
renderInput: ComponentRenderInput;
|
|
204
195
|
rendererCache: Map<string, IntegrationRenderer<any>>;
|
|
205
|
-
}):
|
|
206
|
-
this.
|
|
207
|
-
|
|
196
|
+
}): ForeignChildRuntime {
|
|
197
|
+
this.ForeignChildRuntimeCreationCount += 1;
|
|
198
|
+
|
|
199
|
+
if (this.UseFailFastForeignChildRuntime) {
|
|
200
|
+
return this.createFailFastForeignChildRuntime();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return super.createForeignChildRuntime(options);
|
|
208
204
|
}
|
|
209
205
|
}
|
|
210
206
|
|
|
@@ -439,7 +435,7 @@ describe('IntegrationRenderer', () => {
|
|
|
439
435
|
expect(result.pageLocals).toBe(incomingLocals);
|
|
440
436
|
});
|
|
441
437
|
|
|
442
|
-
it('should include
|
|
438
|
+
it('should include an ownership plan for page, layout, and html template roots', async () => {
|
|
443
439
|
const renderer = new TestIntegrationRenderer({
|
|
444
440
|
appConfig: {
|
|
445
441
|
...AppConfig,
|
|
@@ -505,7 +501,7 @@ describe('IntegrationRenderer', () => {
|
|
|
505
501
|
query: {},
|
|
506
502
|
});
|
|
507
503
|
|
|
508
|
-
expect(result.
|
|
504
|
+
expect(result.ownershipPlan).toEqual(
|
|
509
505
|
expect.objectContaining({
|
|
510
506
|
foreignEdgeCount: 1,
|
|
511
507
|
hasValidationErrors: false,
|
|
@@ -571,8 +567,8 @@ describe('IntegrationRenderer', () => {
|
|
|
571
567
|
query: {},
|
|
572
568
|
});
|
|
573
569
|
|
|
574
|
-
expect(result.
|
|
575
|
-
expect(result.
|
|
570
|
+
expect(result.ownershipPlan?.hasValidationErrors).toBe(true);
|
|
571
|
+
expect(result.ownershipPlan?.validationErrors).toEqual(
|
|
576
572
|
expect.arrayContaining([
|
|
577
573
|
expect.objectContaining({
|
|
578
574
|
code: 'UNKNOWN_INTEGRATION_OWNER',
|
|
@@ -583,7 +579,7 @@ describe('IntegrationRenderer', () => {
|
|
|
583
579
|
);
|
|
584
580
|
});
|
|
585
581
|
|
|
586
|
-
it('should expose a compatibility
|
|
582
|
+
it('should expose a compatibility foreign-subtree payload contract', async () => {
|
|
587
583
|
const renderer = new TestIntegrationRenderer({
|
|
588
584
|
appConfig: AppConfig,
|
|
589
585
|
assetProcessingService: AssetService,
|
|
@@ -600,24 +596,24 @@ describe('IntegrationRenderer', () => {
|
|
|
600
596
|
{
|
|
601
597
|
kind: 'script',
|
|
602
598
|
inline: true,
|
|
603
|
-
content: 'console.log("
|
|
599
|
+
content: 'console.log("foreign-subtree")',
|
|
604
600
|
position: 'body',
|
|
605
601
|
},
|
|
606
602
|
],
|
|
607
603
|
};
|
|
608
604
|
|
|
609
|
-
const payload = await renderer.
|
|
605
|
+
const payload = await renderer.testRenderForeignSubtree({
|
|
610
606
|
component: (() => '<main>Hello</main>') as EcoComponent<Record<string, unknown>>,
|
|
611
607
|
props: {},
|
|
612
608
|
});
|
|
613
609
|
|
|
614
|
-
expect(payload).toEqual<
|
|
610
|
+
expect(payload).toEqual<ForeignSubtreeRenderPayload>({
|
|
615
611
|
html: '<main data-root="true">Hello</main>',
|
|
616
612
|
assets: [
|
|
617
613
|
{
|
|
618
614
|
kind: 'script',
|
|
619
615
|
inline: true,
|
|
620
|
-
content: 'console.log("
|
|
616
|
+
content: 'console.log("foreign-subtree")',
|
|
621
617
|
position: 'body',
|
|
622
618
|
},
|
|
623
619
|
],
|
|
@@ -635,7 +631,7 @@ describe('IntegrationRenderer', () => {
|
|
|
635
631
|
runtimeOrigin: 'http://localhost:3000',
|
|
636
632
|
});
|
|
637
633
|
|
|
638
|
-
const result = renderer.
|
|
634
|
+
const result = renderer.testShouldDelegateForeignChild({
|
|
639
635
|
currentIntegration: 'ghtml',
|
|
640
636
|
targetIntegration: 'react',
|
|
641
637
|
});
|
|
@@ -650,7 +646,7 @@ describe('IntegrationRenderer', () => {
|
|
|
650
646
|
runtimeOrigin: 'http://localhost:3000',
|
|
651
647
|
});
|
|
652
648
|
|
|
653
|
-
const result = renderer.
|
|
649
|
+
const result = renderer.testShouldDelegateForeignChild({
|
|
654
650
|
currentIntegration: 'react',
|
|
655
651
|
targetIntegration: 'react',
|
|
656
652
|
});
|
|
@@ -658,9 +654,9 @@ describe('IntegrationRenderer', () => {
|
|
|
658
654
|
expect(result).toBe(false);
|
|
659
655
|
});
|
|
660
656
|
|
|
661
|
-
it('should delegate foreign component boundaries through the shared
|
|
657
|
+
it('should delegate foreign component boundaries through the shared execution seam', async () => {
|
|
662
658
|
const foreignRenderer = {
|
|
663
|
-
|
|
659
|
+
renderComponentWithForeignChildren: vi.fn(async () => ({
|
|
664
660
|
html: '<aside>Owned by foreign renderer</aside>',
|
|
665
661
|
canAttachAttributes: true,
|
|
666
662
|
rootTag: 'aside',
|
|
@@ -694,14 +690,11 @@ describe('IntegrationRenderer', () => {
|
|
|
694
690
|
};
|
|
695
691
|
|
|
696
692
|
const rendererCache = new Map<string, IntegrationRenderer<any>>();
|
|
697
|
-
const result = await renderer.
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
},
|
|
703
|
-
rendererCache,
|
|
704
|
-
);
|
|
693
|
+
const result = await renderer.renderComponentWithForeignChildren({
|
|
694
|
+
component: ForeignComponent,
|
|
695
|
+
props: { label: 'foreign' },
|
|
696
|
+
integrationContext: { rendererCache },
|
|
697
|
+
});
|
|
705
698
|
|
|
706
699
|
expect(result).toEqual(
|
|
707
700
|
expect.objectContaining({
|
|
@@ -710,12 +703,12 @@ describe('IntegrationRenderer', () => {
|
|
|
710
703
|
}),
|
|
711
704
|
);
|
|
712
705
|
expect(initializeRenderer).toHaveBeenCalledTimes(1);
|
|
713
|
-
expect(foreignRenderer.
|
|
706
|
+
expect(foreignRenderer.renderComponentWithForeignChildren).toHaveBeenCalledTimes(1);
|
|
714
707
|
});
|
|
715
708
|
|
|
716
709
|
it('should preserve shared integration context fields when delegating to the owning renderer', async () => {
|
|
717
710
|
const foreignRenderer = {
|
|
718
|
-
|
|
711
|
+
renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) => ({
|
|
719
712
|
html: `<aside>${String(
|
|
720
713
|
(input.integrationContext as BaseIntegrationContext | undefined)?.componentInstanceId ?? 'missing',
|
|
721
714
|
)}</aside>`,
|
|
@@ -750,29 +743,25 @@ describe('IntegrationRenderer', () => {
|
|
|
750
743
|
},
|
|
751
744
|
};
|
|
752
745
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
{
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
} satisfies BaseIntegrationContext,
|
|
761
|
-
},
|
|
762
|
-
rendererCache,
|
|
763
|
-
);
|
|
746
|
+
await renderer.renderComponentWithForeignChildren({
|
|
747
|
+
component: ForeignComponent,
|
|
748
|
+
props: { label: 'foreign' },
|
|
749
|
+
integrationContext: {
|
|
750
|
+
componentInstanceId: 'host-1',
|
|
751
|
+
} satisfies BaseIntegrationContext,
|
|
752
|
+
});
|
|
764
753
|
|
|
765
|
-
expect(foreignRenderer.
|
|
754
|
+
expect(foreignRenderer.renderComponentWithForeignChildren).toHaveBeenCalledWith(
|
|
766
755
|
expect.objectContaining({
|
|
767
756
|
integrationContext: expect.objectContaining({
|
|
768
757
|
componentInstanceId: 'host-1',
|
|
769
|
-
rendererCache,
|
|
758
|
+
rendererCache: expect.any(Map),
|
|
770
759
|
}),
|
|
771
760
|
}),
|
|
772
761
|
);
|
|
773
762
|
});
|
|
774
763
|
|
|
775
|
-
it('should
|
|
764
|
+
it('should fall back to local rendering when the resolved owner renderer is the current renderer', async () => {
|
|
776
765
|
const renderer = new TestIntegrationRenderer({
|
|
777
766
|
appConfig: {
|
|
778
767
|
...AppConfig,
|
|
@@ -786,6 +775,12 @@ describe('IntegrationRenderer', () => {
|
|
|
786
775
|
assetProcessingService: AssetService,
|
|
787
776
|
runtimeOrigin: 'http://localhost:3000',
|
|
788
777
|
});
|
|
778
|
+
renderer.MockComponentRenderResult = {
|
|
779
|
+
html: '<aside>Local renderer</aside>',
|
|
780
|
+
canAttachAttributes: true,
|
|
781
|
+
rootTag: 'aside',
|
|
782
|
+
integrationName: 'test-renderer',
|
|
783
|
+
};
|
|
789
784
|
|
|
790
785
|
const ForeignComponent = (() => '<aside>Foreign</aside>') as EcoComponent<Record<string, unknown>>;
|
|
791
786
|
ForeignComponent.config = {
|
|
@@ -798,16 +793,13 @@ describe('IntegrationRenderer', () => {
|
|
|
798
793
|
};
|
|
799
794
|
|
|
800
795
|
const rendererCache = new Map<string, IntegrationRenderer<any>>();
|
|
801
|
-
const result = await renderer.
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
},
|
|
807
|
-
rendererCache,
|
|
808
|
-
);
|
|
796
|
+
const result = await renderer.renderComponentWithForeignChildren({
|
|
797
|
+
component: ForeignComponent,
|
|
798
|
+
props: { label: 'foreign' },
|
|
799
|
+
integrationContext: { rendererCache },
|
|
800
|
+
});
|
|
809
801
|
|
|
810
|
-
expect(result).
|
|
802
|
+
expect(result.html).toBe('<aside>Local renderer</aside>');
|
|
811
803
|
});
|
|
812
804
|
|
|
813
805
|
it('should prefer processed lazy script srcUrl for _resolvedLazyTriggers', async () => {
|
|
@@ -1129,7 +1121,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1129
1121
|
rootTag: 'aside',
|
|
1130
1122
|
integrationName: 'explicit-renderer',
|
|
1131
1123
|
})),
|
|
1132
|
-
|
|
1124
|
+
renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
|
|
1133
1125
|
explicitRenderer.renderComponent(input),
|
|
1134
1126
|
),
|
|
1135
1127
|
} as unknown as IntegrationRenderer;
|
|
@@ -1266,7 +1258,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1266
1258
|
).toHaveLength(1);
|
|
1267
1259
|
});
|
|
1268
1260
|
|
|
1269
|
-
it('should fail route execution when unresolved
|
|
1261
|
+
it('should fail route execution when unresolved eco-marker artifact HTML is returned', async () => {
|
|
1270
1262
|
const explicitRenderer = {
|
|
1271
1263
|
renderComponent: vi.fn(async () => ({
|
|
1272
1264
|
html: '<aside>Nested Render</aside>',
|
|
@@ -1282,7 +1274,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1282
1274
|
} as ProcessedAsset,
|
|
1283
1275
|
],
|
|
1284
1276
|
})),
|
|
1285
|
-
|
|
1277
|
+
renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
|
|
1286
1278
|
explicitRenderer.renderComponent(input),
|
|
1287
1279
|
),
|
|
1288
1280
|
} as unknown as IntegrationRenderer;
|
|
@@ -1342,11 +1334,11 @@ describe('IntegrationRenderer', () => {
|
|
|
1342
1334
|
params: {},
|
|
1343
1335
|
query: {},
|
|
1344
1336
|
}),
|
|
1345
|
-
).rejects.toThrow('Full-route unresolved-
|
|
1337
|
+
).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
|
|
1346
1338
|
expect((explicitRenderer.renderComponent as any).mock.calls).toHaveLength(0);
|
|
1347
1339
|
});
|
|
1348
1340
|
|
|
1349
|
-
it('should fail route execution when unresolved
|
|
1341
|
+
it('should fail route execution when unresolved eco-marker artifact HTML remains inside surrounding shell html', async () => {
|
|
1350
1342
|
const explicitRenderer = {
|
|
1351
1343
|
renderComponent: vi.fn(async () => ({
|
|
1352
1344
|
html: '<aside data-explicit-shell="nested"><span>Nested Render</span></aside>',
|
|
@@ -1362,7 +1354,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1362
1354
|
} as ProcessedAsset,
|
|
1363
1355
|
],
|
|
1364
1356
|
})),
|
|
1365
|
-
|
|
1357
|
+
renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
|
|
1366
1358
|
explicitRenderer.renderComponent(input),
|
|
1367
1359
|
),
|
|
1368
1360
|
} as unknown as IntegrationRenderer;
|
|
@@ -1422,11 +1414,11 @@ describe('IntegrationRenderer', () => {
|
|
|
1422
1414
|
params: {},
|
|
1423
1415
|
query: {},
|
|
1424
1416
|
}),
|
|
1425
|
-
).rejects.toThrow('Full-route unresolved-
|
|
1417
|
+
).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
|
|
1426
1418
|
expect((explicitRenderer.renderComponent as any).mock.calls).toHaveLength(0);
|
|
1427
1419
|
});
|
|
1428
1420
|
|
|
1429
|
-
it('should fail route execution for deep multi-level unresolved
|
|
1421
|
+
it('should fail route execution for deep multi-level unresolved eco-marker artifacts', async () => {
|
|
1430
1422
|
const renderOrder: string[] = [];
|
|
1431
1423
|
const explicitRenderer = {
|
|
1432
1424
|
renderComponent: vi.fn(async (input: ComponentRenderInput) => {
|
|
@@ -1459,7 +1451,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1459
1451
|
rootAttributes: { 'data-eco-component-id': 'root-node' },
|
|
1460
1452
|
};
|
|
1461
1453
|
}),
|
|
1462
|
-
|
|
1454
|
+
renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
|
|
1463
1455
|
explicitRenderer.renderComponent(input),
|
|
1464
1456
|
),
|
|
1465
1457
|
} as unknown as IntegrationRenderer;
|
|
@@ -1480,10 +1472,10 @@ describe('IntegrationRenderer', () => {
|
|
|
1480
1472
|
runtimeOrigin: 'http://localhost:3000',
|
|
1481
1473
|
});
|
|
1482
1474
|
|
|
1483
|
-
const _parentMarker =
|
|
1484
|
-
const _leafMarker =
|
|
1475
|
+
const _parentMarker = createUnresolvedMarkerArtifact('n_2', 'parent-component', 'props-parent');
|
|
1476
|
+
const _leafMarker = createUnresolvedMarkerArtifact('n_3', 'leaf-component', 'props-leaf');
|
|
1485
1477
|
|
|
1486
|
-
renderer.RenderedBody = `<html><body><main>${
|
|
1478
|
+
renderer.RenderedBody = `<html><body><main>${createUnresolvedMarkerArtifact('n_1', 'root-component', 'props-root')}</main></body></html>`;
|
|
1487
1479
|
renderer.MockComponentRenderResult = {
|
|
1488
1480
|
html: '<main>Test Page</main>',
|
|
1489
1481
|
canAttachAttributes: true,
|
|
@@ -1541,7 +1533,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1541
1533
|
params: {},
|
|
1542
1534
|
query: {},
|
|
1543
1535
|
}),
|
|
1544
|
-
).rejects.toThrow('Full-route unresolved-
|
|
1536
|
+
).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
|
|
1545
1537
|
expect(renderOrder).toEqual([]);
|
|
1546
1538
|
});
|
|
1547
1539
|
|
|
@@ -1553,7 +1545,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1553
1545
|
rootTag: 'aside',
|
|
1554
1546
|
integrationName: 'explicit-renderer',
|
|
1555
1547
|
})),
|
|
1556
|
-
|
|
1548
|
+
renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
|
|
1557
1549
|
explicitRenderer.renderComponent(input),
|
|
1558
1550
|
),
|
|
1559
1551
|
} as unknown as IntegrationRenderer;
|
|
@@ -1612,10 +1604,10 @@ describe('IntegrationRenderer', () => {
|
|
|
1612
1604
|
params: {},
|
|
1613
1605
|
query: {},
|
|
1614
1606
|
}),
|
|
1615
|
-
).rejects.toThrow('Full-route unresolved-
|
|
1607
|
+
).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
|
|
1616
1608
|
});
|
|
1617
1609
|
|
|
1618
|
-
it('should not recursively resolve
|
|
1610
|
+
it('should not recursively resolve unresolved eco-marker artifacts that were only passed through resolved child html', async () => {
|
|
1619
1611
|
const renderer = new TestIntegrationRenderer({
|
|
1620
1612
|
appConfig: AppConfig,
|
|
1621
1613
|
assetProcessingService: AssetService,
|
|
@@ -1640,7 +1632,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1640
1632
|
};
|
|
1641
1633
|
|
|
1642
1634
|
await expect(
|
|
1643
|
-
renderer.
|
|
1635
|
+
renderer.renderComponentWithForeignChildren({
|
|
1644
1636
|
component: Component,
|
|
1645
1637
|
props: {},
|
|
1646
1638
|
children: '<aside>already resolved child html</aside>',
|
|
@@ -1652,7 +1644,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1652
1644
|
);
|
|
1653
1645
|
});
|
|
1654
1646
|
|
|
1655
|
-
it('fails fast when a renderer without a
|
|
1647
|
+
it('fails fast when a renderer without a foreign-child runtime crosses into a foreign owner', async () => {
|
|
1656
1648
|
const foreignRenderer = {
|
|
1657
1649
|
renderComponent: vi.fn(async () => ({
|
|
1658
1650
|
html: '<span>resolved nested marker</span>',
|
|
@@ -1660,7 +1652,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1660
1652
|
rootTag: 'span',
|
|
1661
1653
|
integrationName: 'foreign-renderer',
|
|
1662
1654
|
})),
|
|
1663
|
-
|
|
1655
|
+
renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
|
|
1664
1656
|
foreignRenderer.renderComponent(input),
|
|
1665
1657
|
),
|
|
1666
1658
|
} as unknown as IntegrationRenderer;
|
|
@@ -1680,6 +1672,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1680
1672
|
assetProcessingService: AssetService,
|
|
1681
1673
|
runtimeOrigin: 'http://localhost:3000',
|
|
1682
1674
|
});
|
|
1675
|
+
renderer.UseFailFastForeignChildRuntime = true;
|
|
1683
1676
|
|
|
1684
1677
|
const ForeignComponent = eco.component<{}, string>({
|
|
1685
1678
|
integration: 'foreign-renderer',
|
|
@@ -1694,19 +1687,23 @@ describe('IntegrationRenderer', () => {
|
|
|
1694
1687
|
render: ({ children }) => `<section>${children ?? ''}${ForeignComponent({})}</section>`,
|
|
1695
1688
|
});
|
|
1696
1689
|
|
|
1697
|
-
const passedThroughMarker =
|
|
1690
|
+
const passedThroughMarker = createUnresolvedMarkerArtifact(
|
|
1691
|
+
'n_passed',
|
|
1692
|
+
'passed-through-component',
|
|
1693
|
+
'p_passed',
|
|
1694
|
+
);
|
|
1698
1695
|
|
|
1699
1696
|
await expect(
|
|
1700
|
-
renderer.
|
|
1697
|
+
renderer.renderComponentWithForeignChildren({
|
|
1701
1698
|
component: ShellComponent,
|
|
1702
1699
|
props: { children: passedThroughMarker },
|
|
1703
1700
|
children: passedThroughMarker,
|
|
1704
1701
|
}),
|
|
1705
|
-
).rejects.toThrow('without a renderer-owned
|
|
1702
|
+
).rejects.toThrow('without a renderer-owned foreign-child runtime');
|
|
1706
1703
|
expect(foreignRenderer.renderComponent).toHaveBeenCalledTimes(0);
|
|
1707
1704
|
});
|
|
1708
1705
|
|
|
1709
|
-
it('fails route execution when deep mixed-integration
|
|
1706
|
+
it('fails route execution when deep mixed-integration eco-marker artifacts are returned at the route level', async () => {
|
|
1710
1707
|
const renderOrder: string[] = [];
|
|
1711
1708
|
const explicitRenderer = {
|
|
1712
1709
|
renderComponent: vi.fn(async (input: ComponentRenderInput) => {
|
|
@@ -1740,7 +1737,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1740
1737
|
integrationName: 'explicit-renderer',
|
|
1741
1738
|
};
|
|
1742
1739
|
}),
|
|
1743
|
-
|
|
1740
|
+
renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
|
|
1744
1741
|
explicitRenderer.renderComponent(input),
|
|
1745
1742
|
),
|
|
1746
1743
|
} as unknown as IntegrationRenderer;
|
|
@@ -1761,10 +1758,10 @@ describe('IntegrationRenderer', () => {
|
|
|
1761
1758
|
runtimeOrigin: 'http://localhost:3000',
|
|
1762
1759
|
});
|
|
1763
1760
|
|
|
1764
|
-
const _parentMarker =
|
|
1765
|
-
const _leafMarker =
|
|
1761
|
+
const _parentMarker = createUnresolvedMarkerArtifact('n_2', 'parent-component', 'props-parent');
|
|
1762
|
+
const _leafMarker = createUnresolvedMarkerArtifact('n_3', 'leaf-component', 'props-leaf');
|
|
1766
1763
|
|
|
1767
|
-
renderer.RenderedBody = `<html><body><main><div data-shell="deep">${
|
|
1764
|
+
renderer.RenderedBody = `<html><body><main><div data-shell="deep">${createUnresolvedMarkerArtifact('n_1', 'root-component', 'props-root')}</div></main></body></html>`;
|
|
1768
1765
|
renderer.MockComponentRenderResult = {
|
|
1769
1766
|
html: '<main>Test Page</main>',
|
|
1770
1767
|
canAttachAttributes: true,
|
|
@@ -1822,7 +1819,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1822
1819
|
params: {},
|
|
1823
1820
|
query: {},
|
|
1824
1821
|
}),
|
|
1825
|
-
).rejects.toThrow('Full-route unresolved-
|
|
1822
|
+
).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
|
|
1826
1823
|
expect(renderOrder).toEqual([]);
|
|
1827
1824
|
});
|
|
1828
1825
|
|
|
@@ -1843,7 +1840,7 @@ describe('IntegrationRenderer', () => {
|
|
|
1843
1840
|
currentIntegration: 'foreign-renderer',
|
|
1844
1841
|
},
|
|
1845
1842
|
async () =>
|
|
1846
|
-
renderer.
|
|
1843
|
+
renderer.renderComponentWithForeignChildren({
|
|
1847
1844
|
component: LeafComponent,
|
|
1848
1845
|
props: {},
|
|
1849
1846
|
}),
|
|
@@ -1851,10 +1848,10 @@ describe('IntegrationRenderer', () => {
|
|
|
1851
1848
|
|
|
1852
1849
|
expect(result.value.html).toBe('<section>Leaf Render</section>');
|
|
1853
1850
|
expect(result.value.html).not.toContain('<eco-marker');
|
|
1854
|
-
expect(renderer.
|
|
1851
|
+
expect(renderer.ForeignChildRuntimeCreationCount).toBe(0);
|
|
1855
1852
|
});
|
|
1856
1853
|
|
|
1857
|
-
it('uses inline partial rendering when no foreign
|
|
1854
|
+
it('uses inline partial rendering when no foreign children are present', async () => {
|
|
1858
1855
|
const renderer = new TestIntegrationRenderer({
|
|
1859
1856
|
appConfig: AppConfig,
|
|
1860
1857
|
assetProcessingService: AssetService,
|
|
@@ -1882,10 +1879,10 @@ describe('IntegrationRenderer', () => {
|
|
|
1882
1879
|
|
|
1883
1880
|
expect(await response.text()).toBe('<section>Inline</section>');
|
|
1884
1881
|
expect(inlineRenderCount).toBe(1);
|
|
1885
|
-
expect(renderer.
|
|
1882
|
+
expect(renderer.ForeignChildRenderCount).toBe(0);
|
|
1886
1883
|
});
|
|
1887
1884
|
|
|
1888
|
-
it('falls back to
|
|
1885
|
+
it('falls back to foreign-subtree partial rendering when compatibility is needed', async () => {
|
|
1889
1886
|
const renderer = new TestIntegrationRenderer({
|
|
1890
1887
|
appConfig: AppConfig,
|
|
1891
1888
|
assetProcessingService: AssetService,
|
|
@@ -1901,18 +1898,18 @@ describe('IntegrationRenderer', () => {
|
|
|
1901
1898
|
},
|
|
1902
1899
|
};
|
|
1903
1900
|
|
|
1904
|
-
const View = (() => '<section>
|
|
1901
|
+
const View = (() => '<section>Foreign Subtree</section>') as EcoComponent<Record<string, unknown>>;
|
|
1905
1902
|
View.config = {
|
|
1906
1903
|
integration: 'test-renderer',
|
|
1907
1904
|
__eco: {
|
|
1908
|
-
id: '
|
|
1909
|
-
file: '/app/components/
|
|
1905
|
+
id: 'foreign-subtree-view',
|
|
1906
|
+
file: '/app/components/foreign-subtree-view.ts',
|
|
1910
1907
|
integration: 'test-renderer',
|
|
1911
1908
|
},
|
|
1912
1909
|
dependencies: { components: [ForeignChild] },
|
|
1913
1910
|
};
|
|
1914
1911
|
renderer.MockComponentRenderResult = {
|
|
1915
|
-
html: '<section>
|
|
1912
|
+
html: '<section>Foreign Subtree</section>',
|
|
1916
1913
|
canAttachAttributes: true,
|
|
1917
1914
|
rootTag: 'section',
|
|
1918
1915
|
integrationName: 'test-renderer',
|
|
@@ -1928,14 +1925,14 @@ describe('IntegrationRenderer', () => {
|
|
|
1928
1925
|
},
|
|
1929
1926
|
});
|
|
1930
1927
|
|
|
1931
|
-
expect(await response.text()).toBe('<section>
|
|
1928
|
+
expect(await response.text()).toBe('<section>Foreign Subtree</section>');
|
|
1932
1929
|
expect(inlineRenderCount).toBe(0);
|
|
1933
|
-
expect(renderer.
|
|
1930
|
+
expect(renderer.ForeignChildRenderCount).toBe(1);
|
|
1934
1931
|
});
|
|
1935
1932
|
|
|
1936
1933
|
it('reuses one foreign renderer instance across shared view shell composition', async () => {
|
|
1937
1934
|
const foreignRenderer = {
|
|
1938
|
-
|
|
1935
|
+
renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) => {
|
|
1939
1936
|
const componentId = input.component.config?.__eco?.id;
|
|
1940
1937
|
|
|
1941
1938
|
if (componentId === 'foreign-html-template') {
|
|
@@ -2020,10 +2017,10 @@ describe('IntegrationRenderer', () => {
|
|
|
2020
2017
|
|
|
2021
2018
|
expect(await response.text()).toContain('<main><section>Foreign View</section></main>');
|
|
2022
2019
|
expect(initializeRenderer).toHaveBeenCalledTimes(1);
|
|
2023
|
-
expect(foreignRenderer.
|
|
2020
|
+
expect(foreignRenderer.renderComponentWithForeignChildren).toHaveBeenCalledTimes(3);
|
|
2024
2021
|
});
|
|
2025
2022
|
|
|
2026
|
-
it('should skip foreign-
|
|
2023
|
+
it('should skip foreign-child wrapping for pure same-integration component trees', () => {
|
|
2027
2024
|
const renderer = new TestIntegrationRenderer({
|
|
2028
2025
|
appConfig: AppConfig,
|
|
2029
2026
|
assetProcessingService: AssetService,
|
|
@@ -2051,7 +2048,7 @@ describe('IntegrationRenderer', () => {
|
|
|
2051
2048
|
dependencies: { components: [Child] },
|
|
2052
2049
|
};
|
|
2053
2050
|
|
|
2054
|
-
expect(renderer.
|
|
2051
|
+
expect(renderer.testHasForeignChildDescendants(Root)).toBe(false);
|
|
2055
2052
|
});
|
|
2056
2053
|
|
|
2057
2054
|
it('should detect nested cross-integration component trees', () => {
|
|
@@ -2082,7 +2079,7 @@ describe('IntegrationRenderer', () => {
|
|
|
2082
2079
|
dependencies: { components: [ForeignChild] },
|
|
2083
2080
|
};
|
|
2084
2081
|
|
|
2085
|
-
expect(renderer.
|
|
2082
|
+
expect(renderer.testHasForeignChildDescendants(Root)).toBe(true);
|
|
2086
2083
|
});
|
|
2087
2084
|
});
|
|
2088
2085
|
});
|