@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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { EcoPagesAppConfig } from '../types/public-types.ts';
|
|
2
|
+
import {
|
|
3
|
+
DevelopmentInvalidationService,
|
|
4
|
+
type DevelopmentInvalidationPlan,
|
|
5
|
+
} from '../services/invalidation/development-invalidation.service.ts';
|
|
6
|
+
import { setHostModuleLoader } from '../services/module-loading/host-module-loader-registry.ts';
|
|
7
|
+
|
|
8
|
+
export type HostRuntimeModuleLoader = (id: string) => Promise<unknown>;
|
|
9
|
+
|
|
10
|
+
export interface DevelopmentHostRuntime {
|
|
11
|
+
registerHostModuleLoader(loader: HostRuntimeModuleLoader): void;
|
|
12
|
+
planFileChange(filePath: string): DevelopmentInvalidationPlan;
|
|
13
|
+
invalidateServerModules(changedFiles?: string[]): void;
|
|
14
|
+
resetRuntimeState(changedFiles?: string[]): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createDevelopmentHostRuntime(appConfig: EcoPagesAppConfig): DevelopmentHostRuntime {
|
|
18
|
+
const invalidationService = new DevelopmentInvalidationService(appConfig);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
registerHostModuleLoader(loader) {
|
|
22
|
+
setHostModuleLoader(loader);
|
|
23
|
+
},
|
|
24
|
+
planFileChange(filePath) {
|
|
25
|
+
return invalidationService.planFileChange(filePath);
|
|
26
|
+
},
|
|
27
|
+
invalidateServerModules(changedFiles) {
|
|
28
|
+
invalidationService.invalidateServerModules(changedFiles);
|
|
29
|
+
},
|
|
30
|
+
resetRuntimeState(changedFiles) {
|
|
31
|
+
invalidationService.resetRuntimeState(changedFiles);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FileRouteMiddleware } from '../types/public-types.ts';
|
|
3
3
|
import { eco } from './eco.browser.ts';
|
|
4
4
|
|
|
5
5
|
describe('browser eco facade', () => {
|
|
@@ -10,7 +10,7 @@ describe('browser eco facade', () => {
|
|
|
10
10
|
|
|
11
11
|
const staticPaths = async () => ({ paths: [{ params: { slug: 'intro' } }] });
|
|
12
12
|
const metadata = async () => ({ title: 'Docs', description: 'Docs page' });
|
|
13
|
-
const middleware:
|
|
13
|
+
const middleware: FileRouteMiddleware[] = [async (_ctx, next) => next()];
|
|
14
14
|
|
|
15
15
|
const Page = eco.page({
|
|
16
16
|
layout: Layout,
|
package/src/eco/eco.browser.ts
CHANGED
|
@@ -4,10 +4,10 @@ import type {
|
|
|
4
4
|
EcoLayoutComponent,
|
|
5
5
|
EcoPagesElement,
|
|
6
6
|
EcoPageComponent,
|
|
7
|
+
FileRouteMiddleware,
|
|
7
8
|
GetMetadata,
|
|
8
9
|
GetStaticPaths,
|
|
9
10
|
GetStaticProps,
|
|
10
|
-
Middleware,
|
|
11
11
|
RequestPageContext,
|
|
12
12
|
} from '../types/public-types.ts';
|
|
13
13
|
import type { CacheStrategy } from '../services/cache/cache.types.ts';
|
|
@@ -38,7 +38,7 @@ function layout<E = EcoPagesElement>(options: LayoutOptions<E>): EcoLayoutCompon
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function page<T, E>(
|
|
41
|
-
options: PageOptionsBase<T, E> & { cache?: CacheStrategy; middleware?:
|
|
41
|
+
options: PageOptionsBase<T, E> & { cache?: CacheStrategy; middleware?: FileRouteMiddleware[] },
|
|
42
42
|
): EcoPageComponent<T> {
|
|
43
43
|
const {
|
|
44
44
|
layout: pageLayout,
|
package/src/eco/eco.test.ts
CHANGED
|
@@ -13,16 +13,16 @@ import type {
|
|
|
13
13
|
} from '../types/public-types.ts';
|
|
14
14
|
import type { EcoPagesAppConfig } from '../types/internal-types.ts';
|
|
15
15
|
import {
|
|
16
|
-
type
|
|
16
|
+
type ForeignChildRuntime,
|
|
17
17
|
getComponentRenderContext,
|
|
18
18
|
runWithComponentRenderContext,
|
|
19
19
|
} from '../route-renderer/orchestration/component-render-context.ts';
|
|
20
20
|
|
|
21
21
|
const mockAppConfig = {} as EcoPagesAppConfig;
|
|
22
22
|
|
|
23
|
-
function
|
|
23
|
+
function createResolvedForeignChildRuntime(targetIntegrations: string[], value: string): ForeignChildRuntime {
|
|
24
24
|
return {
|
|
25
|
-
|
|
25
|
+
interceptForeignChild: async ({ targetIntegration }: { targetIntegration?: string }) =>
|
|
26
26
|
targetIntegration !== undefined && targetIntegrations.includes(targetIntegration)
|
|
27
27
|
? ({ kind: 'resolved', value } as const)
|
|
28
28
|
: ({ kind: 'inline' } as const),
|
|
@@ -204,7 +204,7 @@ describe('eco namespace', () => {
|
|
|
204
204
|
expect(Component.config?.integration).toBe('lit');
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
-
test('should render inline when
|
|
207
|
+
test('should render inline when the foreign-child runtime returns inline for a React component', async () => {
|
|
208
208
|
const ReactButton = eco.component({
|
|
209
209
|
integration: 'react',
|
|
210
210
|
__eco: {
|
|
@@ -225,7 +225,7 @@ describe('eco namespace', () => {
|
|
|
225
225
|
expect(execution.value).toBe('<button type="button">Click</button>');
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
-
test('should resolve foreign
|
|
228
|
+
test('should resolve foreign children immediately when the runtime returns resolved output', async () => {
|
|
229
229
|
const ReactButton = eco.component({
|
|
230
230
|
integration: 'react',
|
|
231
231
|
__eco: {
|
|
@@ -239,7 +239,7 @@ describe('eco namespace', () => {
|
|
|
239
239
|
const execution = await runWithComponentRenderContext(
|
|
240
240
|
{
|
|
241
241
|
currentIntegration: 'lit',
|
|
242
|
-
|
|
242
|
+
foreignChildRuntime: createResolvedForeignChildRuntime(
|
|
243
243
|
['react'],
|
|
244
244
|
'<aside>Resolved in owning renderer</aside>',
|
|
245
245
|
),
|
package/src/eco/eco.ts
CHANGED
|
@@ -9,10 +9,10 @@ import type {
|
|
|
9
9
|
EcoLayoutComponent,
|
|
10
10
|
EcoPagesElement,
|
|
11
11
|
EcoPageComponent,
|
|
12
|
+
FileRouteMiddleware,
|
|
12
13
|
GetMetadata,
|
|
13
14
|
GetStaticPaths,
|
|
14
15
|
GetStaticProps,
|
|
15
|
-
Middleware,
|
|
16
16
|
RequestLocals,
|
|
17
17
|
RequestPageContext,
|
|
18
18
|
} from '../types/public-types.ts';
|
|
@@ -30,19 +30,19 @@ import type {
|
|
|
30
30
|
} from './eco.types.ts';
|
|
31
31
|
import {
|
|
32
32
|
finalizeComponentRender,
|
|
33
|
-
|
|
33
|
+
interceptForeignChild,
|
|
34
34
|
} from '../route-renderer/orchestration/component-render-context.ts';
|
|
35
35
|
import { isThenable } from '../route-renderer/orchestration/render-output.utils.ts';
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Creates a component factory with lazy-trigger support and
|
|
38
|
+
* Creates a component factory with lazy-trigger support and foreign-child-runtime
|
|
39
39
|
* interception.
|
|
40
40
|
*
|
|
41
41
|
* Behavior:
|
|
42
42
|
* - In normal render flow, returns `options.render(props)` with optional lazy
|
|
43
43
|
* trigger/script wrapping.
|
|
44
|
-
* - When rendering under an active
|
|
45
|
-
* renderer-owned
|
|
44
|
+
* - When rendering under an active foreign-child runtime and the current
|
|
45
|
+
* renderer-owned foreign-child runtime resolves the foreign child immediately, returns
|
|
46
46
|
* that resolved output instead of rendering the component inline.
|
|
47
47
|
*
|
|
48
48
|
* @param options Component options for rendering and dependency declaration.
|
|
@@ -53,20 +53,20 @@ function createComponentFactory<P, E>(options: ComponentOptions<P, E>): EcoCompo
|
|
|
53
53
|
const comp: EcoComponent<P, E> = ((props: P) => {
|
|
54
54
|
const componentProps = (props ?? {}) as Record<string, unknown>;
|
|
55
55
|
const renderInline = () => finalizeComponentRender(comp, options.render(props)) as E;
|
|
56
|
-
const
|
|
56
|
+
const foreignChildRender = interceptForeignChild({
|
|
57
57
|
component: comp,
|
|
58
58
|
props: componentProps,
|
|
59
59
|
targetIntegration: integrationName,
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
if (isThenable<unknown | undefined>(
|
|
63
|
-
return
|
|
64
|
-
|
|
62
|
+
if (isThenable<unknown | undefined>(foreignChildRender)) {
|
|
63
|
+
return foreignChildRender.then((resolvedForeignChildRender) =>
|
|
64
|
+
resolvedForeignChildRender !== undefined ? (resolvedForeignChildRender as E) : renderInline(),
|
|
65
65
|
) as E;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
if (
|
|
69
|
-
return
|
|
68
|
+
if (foreignChildRender !== undefined) {
|
|
69
|
+
return foreignChildRender as E;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
return renderInline();
|
|
@@ -129,7 +129,7 @@ function page<T = {}, E = EcoPagesElement, const K extends keyof RequestLocals =
|
|
|
129
129
|
* @returns Eco page component.
|
|
130
130
|
*/
|
|
131
131
|
function page<T, E>(
|
|
132
|
-
options: PageOptionsBase<T, E> & { cache?: CacheStrategy; middleware?:
|
|
132
|
+
options: PageOptionsBase<T, E> & { cache?: CacheStrategy; middleware?: FileRouteMiddleware[] },
|
|
133
133
|
): EcoPageComponent<T> {
|
|
134
134
|
const { layout, dependencies, render, staticPaths, staticProps, metadata, cache, requires, middleware } = options;
|
|
135
135
|
|
package/src/eco/eco.types.ts
CHANGED
|
@@ -12,12 +12,12 @@ import type {
|
|
|
12
12
|
EcoLayoutComponent,
|
|
13
13
|
EcoPageLayoutComponent,
|
|
14
14
|
EcoPagesElement,
|
|
15
|
+
FileRouteMiddleware,
|
|
15
16
|
GetMetadata,
|
|
16
17
|
GetStaticPaths,
|
|
17
18
|
GetStaticProps,
|
|
18
19
|
HtmlTemplateProps,
|
|
19
20
|
LayoutProps,
|
|
20
|
-
Middleware,
|
|
21
21
|
RequestLocals,
|
|
22
22
|
RequestPageContext,
|
|
23
23
|
} from '../types/public-types.ts';
|
|
@@ -119,7 +119,7 @@ interface PageOptionsWithMiddleware<T, E = EcoPagesElement> extends PageOptionsB
|
|
|
119
119
|
* Request-time middleware for file-based routes.
|
|
120
120
|
* Runs before rendering and can short-circuit by returning a Response.
|
|
121
121
|
*/
|
|
122
|
-
middleware:
|
|
122
|
+
middleware: FileRouteMiddleware[];
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
@@ -164,7 +164,7 @@ export type EcoPageComponent<T> = EcoComponent<PagePropsFor<T> & Partial<Request
|
|
|
164
164
|
metadata?: GetMetadata<T>;
|
|
165
165
|
cache?: CacheStrategy;
|
|
166
166
|
requires?: PageRequires;
|
|
167
|
-
middleware?:
|
|
167
|
+
middleware?: FileRouteMiddleware[];
|
|
168
168
|
};
|
|
169
169
|
|
|
170
170
|
/**
|
package/src/errors/index.ts
CHANGED
|
@@ -52,7 +52,9 @@ interface HMRPayload {
|
|
|
52
52
|
break;
|
|
53
53
|
case 'layout-update': {
|
|
54
54
|
await waitForNavigationToSettle(navigationRuntime);
|
|
55
|
-
if (
|
|
55
|
+
if (
|
|
56
|
+
await navigationRuntime.reloadCurrentPage({ clearCache: true, moduleUrl: getActiveHmrModuleUrl() })
|
|
57
|
+
) {
|
|
56
58
|
} else {
|
|
57
59
|
location.reload();
|
|
58
60
|
}
|
|
@@ -107,7 +109,7 @@ interface HMRPayload {
|
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
const handlerPaths = Object.keys(window.__ECO_PAGES__?.hmrHandlers ?? {});
|
|
110
|
-
return handlerPaths.
|
|
112
|
+
return handlerPaths[handlerPaths.length - 1];
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
async function waitForNavigationToSettle(navigationRuntime: ReturnType<typeof getEcoNavigationRuntime>) {
|
|
@@ -12,7 +12,6 @@ const SRC_DIR = path.join(TMP_DIR, 'src');
|
|
|
12
12
|
function createMockContext(overrides: Partial<JsHmrContext> = {}): JsHmrContext {
|
|
13
13
|
return {
|
|
14
14
|
getWatchedFiles: () => new Map(),
|
|
15
|
-
getSpecifierMap: () => new Map(),
|
|
16
15
|
getEntrypointDependencyGraph: () => new NoopDevGraphService(),
|
|
17
16
|
getDistDir: () => TMP_DIR,
|
|
18
17
|
getPlugins: () => [],
|
|
@@ -26,11 +26,6 @@ export interface JsHmrContext {
|
|
|
26
26
|
*/
|
|
27
27
|
getWatchedFiles(): Map<string, string>;
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* Map of bare specifiers to vendor URLs for import resolution.
|
|
31
|
-
*/
|
|
32
|
-
getSpecifierMap(): Map<string, string>;
|
|
33
|
-
|
|
34
29
|
getEntrypointDependencyGraph(): EntrypointDependencyGraph;
|
|
35
30
|
|
|
36
31
|
/**
|
|
@@ -101,7 +96,6 @@ export interface JsHmrContext {
|
|
|
101
96
|
* ```typescript
|
|
102
97
|
* const context = {
|
|
103
98
|
* getWatchedFiles: () => watchedFilesMap,
|
|
104
|
-
* getSpecifierMap: () => specifierMap,
|
|
105
99
|
* getDistDir: () => '/path/to/dist/_hmr',
|
|
106
100
|
* getPlugins: () => [],
|
|
107
101
|
* getSrcDir: () => '/path/to/src'
|
|
@@ -3,7 +3,7 @@ import HtmlTemplate from '../../../__fixtures__/app/src/includes/html.ghtml.js';
|
|
|
3
3
|
import { FIXTURE_APP_PROJECT_DIR } from '../../../__fixtures__/constants.js';
|
|
4
4
|
import {
|
|
5
5
|
eco,
|
|
6
|
-
type
|
|
6
|
+
type ForeignSubtreeRenderPayload,
|
|
7
7
|
type EcoComponent,
|
|
8
8
|
type EcoPagesElement,
|
|
9
9
|
type HtmlTemplateProps,
|
|
@@ -121,7 +121,7 @@ describe('GhtmlRenderer', () => {
|
|
|
121
121
|
).rejects.toThrow('Error rendering page: Page failed to render');
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
-
it('should resolve deferred foreign layout content without unresolved
|
|
124
|
+
it('should resolve deferred foreign layout content without unresolved eco-marker artifacts', async () => {
|
|
125
125
|
const deferredPlugin = new DeferredPlugin();
|
|
126
126
|
const config = await new ConfigBuilder()
|
|
127
127
|
.setRootDir(FIXTURE_APP_PROJECT_DIR)
|
|
@@ -177,17 +177,17 @@ describe('GhtmlRenderer', () => {
|
|
|
177
177
|
expect(body).not.toContain('<eco-marker');
|
|
178
178
|
});
|
|
179
179
|
|
|
180
|
-
it('should expose the compatibility
|
|
180
|
+
it('should expose the compatibility foreign-subtree payload contract', async () => {
|
|
181
181
|
const renderer = createRenderer();
|
|
182
|
-
const Component = (async () => '<main>
|
|
182
|
+
const Component = (async () => '<main>Foreign Subtree</main>') as EcoComponent<Record<string, unknown>>;
|
|
183
183
|
|
|
184
|
-
const result = await renderer.
|
|
184
|
+
const result = await renderer.renderForeignSubtree({
|
|
185
185
|
component: Component,
|
|
186
186
|
props: {},
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
-
expect(result).toEqual<
|
|
190
|
-
html: '<main>
|
|
189
|
+
expect(result).toEqual<ForeignSubtreeRenderPayload>({
|
|
190
|
+
html: '<main>Foreign Subtree</main>',
|
|
191
191
|
assets: [],
|
|
192
192
|
rootTag: 'main',
|
|
193
193
|
rootAttributes: undefined,
|
|
@@ -30,22 +30,12 @@ export class GhtmlRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
|
30
30
|
name = GHTML_PLUGIN_NAME;
|
|
31
31
|
|
|
32
32
|
override async renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult> {
|
|
33
|
-
return this.
|
|
33
|
+
return this.renderStringComponentWithQueuedForeignSubtrees(
|
|
34
34
|
input,
|
|
35
35
|
input.component as GhtmlViewFn<Record<string, unknown>>,
|
|
36
36
|
);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
protected override createComponentBoundaryRuntime(options: {
|
|
40
|
-
boundaryInput: ComponentRenderInput;
|
|
41
|
-
rendererCache: Map<string, IntegrationRenderer<any>>;
|
|
42
|
-
}) {
|
|
43
|
-
return this.createQueuedBoundaryRuntime({
|
|
44
|
-
boundaryInput: options.boundaryInput,
|
|
45
|
-
rendererCache: options.rendererCache,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
39
|
async render({
|
|
50
40
|
params,
|
|
51
41
|
query,
|
|
@@ -95,11 +95,10 @@ describe('IntegrationPlugin', () => {
|
|
|
95
95
|
await expect(plugin.teardown()).resolves.toBeUndefined();
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
it('should register
|
|
99
|
-
const
|
|
98
|
+
it('should register HMR strategies through the base HMR setup', () => {
|
|
99
|
+
const registerStrategy = vi.fn();
|
|
100
100
|
const hmrManager = {
|
|
101
|
-
|
|
102
|
-
registerStrategy: vi.fn(),
|
|
101
|
+
registerStrategy,
|
|
103
102
|
registerEntrypoint: vi.fn(),
|
|
104
103
|
registerScriptEntrypoint: vi.fn(),
|
|
105
104
|
setPlugins: vi.fn(),
|
|
@@ -108,26 +107,22 @@ describe('IntegrationPlugin', () => {
|
|
|
108
107
|
broadcast: vi.fn(),
|
|
109
108
|
getOutputUrl: vi.fn(),
|
|
110
109
|
getWatchedFiles: vi.fn(() => new Map()),
|
|
111
|
-
getSpecifierMap: vi.fn(() => new Map()),
|
|
112
110
|
getDistDir: vi.fn(() => ''),
|
|
113
111
|
getPlugins: vi.fn(() => []),
|
|
114
112
|
getDefaultContext: vi.fn(),
|
|
115
113
|
handleFileChange: vi.fn(),
|
|
116
114
|
} satisfies IHmrManager;
|
|
117
115
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
};
|
|
116
|
+
const strategy = { matches: vi.fn(() => false), process: vi.fn(), priority: 10, type: 'integration' } as any;
|
|
117
|
+
const pluginWithStrategy = new (class extends TestIntegrationPlugin {
|
|
118
|
+
override getHmrStrategy() {
|
|
119
|
+
return strategy;
|
|
123
120
|
}
|
|
124
121
|
})(config);
|
|
125
122
|
|
|
126
|
-
|
|
123
|
+
pluginWithStrategy.setHmrManager(hmrManager);
|
|
127
124
|
|
|
128
|
-
expect(
|
|
129
|
-
'test-runtime': '/assets/vendors/test-runtime.js',
|
|
130
|
-
});
|
|
125
|
+
expect(registerStrategy).toHaveBeenCalledWith(strategy);
|
|
131
126
|
});
|
|
132
127
|
|
|
133
128
|
it('should stamp the plugin integration name onto initialized renderers', () => {
|
|
@@ -5,15 +5,45 @@ import type { EcoPagesElement } from '../types/public-types.ts';
|
|
|
5
5
|
import type { IntegrationRenderer } from '../route-renderer/orchestration/integration-renderer.ts';
|
|
6
6
|
import { AssetProcessingService } from '../services/assets/asset-processing-service/asset-processing.service.ts';
|
|
7
7
|
import type { AssetDefinition, ProcessedAsset } from '../services/assets/asset-processing-service/assets.types.ts';
|
|
8
|
+
import { deepMerge } from '../utils/deep-merge.ts';
|
|
9
|
+
import { invariant } from '../utils/invariant.ts';
|
|
8
10
|
import type { RuntimeCapabilityDeclaration } from './runtime-capability.ts';
|
|
9
11
|
|
|
10
12
|
export type { RuntimeCapabilityDeclaration, RuntimeCapabilityTag } from './runtime-capability.ts';
|
|
13
|
+
export type {
|
|
14
|
+
EcoBuildLoader,
|
|
15
|
+
EcoBuildOnLoadArgs,
|
|
16
|
+
EcoBuildOnLoadResult,
|
|
17
|
+
EcoBuildOnResolveArgs,
|
|
18
|
+
EcoBuildOnResolveResult,
|
|
19
|
+
EcoBuildPlugin,
|
|
20
|
+
EcoBuildPluginBuilder,
|
|
21
|
+
} from '../build/build-types.ts';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Type-erased integration plugin stored in app-level registries.
|
|
25
|
+
*
|
|
26
|
+
* Ecopages keeps one heterogeneous integration list, while each plugin and
|
|
27
|
+
* renderer still owns its framework-specific render payload type internally.
|
|
28
|
+
*/
|
|
29
|
+
export type AnyIntegrationPlugin = IntegrationPlugin<unknown>;
|
|
11
30
|
|
|
12
31
|
export const INTEGRATION_PLUGIN_ERRORS = {
|
|
13
32
|
NOT_INITIALIZED_WITH_APP_CONFIG: 'Plugin not initialized with app config',
|
|
14
33
|
NOT_INITIALIZED_WITH_ASSET_SERVICE: 'Plugin not initialized with asset dependency service',
|
|
15
34
|
} as const;
|
|
16
35
|
|
|
36
|
+
export function mergeIntegrationOptions<TDefaults, TOverrides>(
|
|
37
|
+
defaults: TDefaults,
|
|
38
|
+
overrides: TOverrides,
|
|
39
|
+
): TDefaults & TOverrides {
|
|
40
|
+
return deepMerge(defaults, overrides);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function assertIntegrationInvariant(condition: boolean, message?: string): asserts condition {
|
|
44
|
+
invariant(condition, message);
|
|
45
|
+
}
|
|
46
|
+
|
|
17
47
|
/**
|
|
18
48
|
* Base configuration shared by all integration plugins.
|
|
19
49
|
*
|
|
@@ -157,40 +187,22 @@ export abstract class IntegrationPlugin<C = EcoPagesElement> {
|
|
|
157
187
|
* ```typescript
|
|
158
188
|
* getHmrStrategy(): HmrStrategy {
|
|
159
189
|
* const context = this.hmrManager!.getDefaultContext();
|
|
160
|
-
* return new ReactHmrStrategy(context);
|
|
190
|
+
* return new ReactHmrStrategy({ context, pageMetadataCache, runtimeAliasMap });
|
|
161
191
|
* }
|
|
162
192
|
* ```
|
|
163
193
|
*/
|
|
164
194
|
getHmrStrategy?(): HmrStrategy | undefined;
|
|
165
195
|
|
|
166
|
-
/**
|
|
167
|
-
* Returns bare-specifier mappings that should be registered in the active
|
|
168
|
-
* runtime specifier registry.
|
|
169
|
-
*
|
|
170
|
-
* @remarks
|
|
171
|
-
* Integrations that own browser runtime bundles can override this to expose
|
|
172
|
-
* stable bare specifiers for client-side imports.
|
|
173
|
-
*
|
|
174
|
-
* Today these mappings are consumed by the development runtime and browser
|
|
175
|
-
* bundle aliasing path. They are intentionally generic enough to grow into a
|
|
176
|
-
* broader import-map-style facility later without moving framework-specific
|
|
177
|
-
* map contents into core.
|
|
178
|
-
*/
|
|
179
|
-
getRuntimeSpecifierMap(): Record<string, string> {
|
|
180
|
-
return {};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
196
|
/**
|
|
184
197
|
* Attaches the shared HMR manager and registers integration-owned development hooks.
|
|
185
198
|
*
|
|
186
199
|
* @remarks
|
|
187
|
-
* The default implementation registers
|
|
188
|
-
*
|
|
189
|
-
*
|
|
200
|
+
* The default implementation registers the optional integration HMR strategy.
|
|
201
|
+
* Integrations should override this only when they need to extend that shared
|
|
202
|
+
* behavior rather than replace it.
|
|
190
203
|
*/
|
|
191
204
|
setHmrManager(hmrManager: IHmrManager) {
|
|
192
205
|
this.hmrManager = hmrManager;
|
|
193
|
-
hmrManager.registerSpecifierMap(this.getRuntimeSpecifierMap());
|
|
194
206
|
|
|
195
207
|
const strategy = this.getHmrStrategy?.();
|
|
196
208
|
if (strategy) {
|
package/src/plugins/processor.ts
CHANGED
|
@@ -5,14 +5,31 @@ import type { EcoPagesAppConfig, IClientBridge } from '../types/internal-types.t
|
|
|
5
5
|
import type { AssetDefinition } from '../services/assets/asset-processing-service/assets.types.ts';
|
|
6
6
|
import { DEFAULT_ECOPAGES_WORK_DIR } from '../config/constants.ts';
|
|
7
7
|
import { GENERATED_BASE_PATHS } from '../config/constants.ts';
|
|
8
|
+
import { deepMerge } from '../utils/deep-merge.ts';
|
|
8
9
|
import type { RuntimeCapabilityDeclaration } from './runtime-capability.ts';
|
|
9
10
|
|
|
10
11
|
export type { RuntimeCapabilityDeclaration, RuntimeCapabilityTag } from './runtime-capability.ts';
|
|
12
|
+
export type {
|
|
13
|
+
EcoBuildLoader,
|
|
14
|
+
EcoBuildOnLoadArgs,
|
|
15
|
+
EcoBuildOnLoadResult,
|
|
16
|
+
EcoBuildOnResolveArgs,
|
|
17
|
+
EcoBuildOnResolveResult,
|
|
18
|
+
EcoBuildPlugin,
|
|
19
|
+
EcoBuildPluginBuilder,
|
|
20
|
+
} from '../build/build-types.ts';
|
|
11
21
|
|
|
12
22
|
export const PROCESSOR_ERRORS = {
|
|
13
23
|
CACHE_DIRECTORY_NOT_SET: 'Cache directory not set in context',
|
|
14
24
|
} as const;
|
|
15
25
|
|
|
26
|
+
export function mergeProcessorOptions<TDefaults, TOverrides>(
|
|
27
|
+
defaults: TDefaults,
|
|
28
|
+
overrides: TOverrides,
|
|
29
|
+
): TDefaults & TOverrides {
|
|
30
|
+
return deepMerge(defaults, overrides);
|
|
31
|
+
}
|
|
32
|
+
|
|
16
33
|
function resolveGeneratedPath(
|
|
17
34
|
type: keyof typeof GENERATED_BASE_PATHS,
|
|
18
35
|
options: { root: string; module: string; subPath?: string },
|